obtenido de aqui
Patrón Inversión de Control (IoC)
La Inversión de Control es un patrón de diseño pensado para permitir un menor acoplamiento entre componentes de una aplicación y fomentar así el reuso de los mismos.
Un problema, una solución
Como todo patrón, comienza planteando un problema y el viene a resolver.
Muchas veces, un componente tiene dependencias de servicios o componentes cuyos tipos concretos son especificados en tiempo de diseño.
Muchas veces, un componente tiene dependencias de servicios o componentes cuyos tipos concretos son especificados en tiempo de diseño.
En el ejemplo de la imagen, clase A depende de ServiceA y de ServiceB.
Los problemas que esto plantea son:
- Al reemplazar o actualizar las dependencias, se necesita cambiar el código fuente de la clase A.
- Las implementaciones concretas de las dependencias tienen que estar disponibles en tiempo de compilación.
- Las clases son difíciles de testear aisladamente porque tienen directas definiciones a sus dependencias. Esto significa que las dependencias no pueden ser reemplazadas por componentes stubs o mocks.
- Las clases tienen código repetido para crear, localizar y gestionar sus dependencias.
Aquí la solución pasa por delegar la función de seleccionar una implementación concreta de las dependencias a un componente externo.
El control de cómo un objeto A obtiene la referencia de un objeto B es invertido. El objeto A no es responsable de obtener una referencia al objeto B sino que es el Componente Externo el responsable de esto. Esta es la base del patrón IoC.
El patrón IOC aplica un principio de diseño denominado principio de Hollywood (No nos llames, nosotros te llamaremos).
Usos
El patrón IoC se puede utilizar cuando:
- Se desee desacoplar las clases de sus dependencias de manera de que las mismas puedan ser reemplazadas o actualizadas con muy pocos o casi ningún cambio en el código fuete de sus clases.
- Desea escribir clases que dependan de clases cuyas implementaciones no son conocidas en tiempo de compilación.
- Desea testar las clases aisladamente sin sus dependencias.
- Desea desacoplar sus clases de ser responsables de localizar y gestionar el tiempo de vida de sus dependencias.
Técnicas de implementación
Según diversos enfoques, este patrón puede implementarse de diversas maneras. Entre las más conocidas tenemos:
- Inyección de dependencias
- Service Locutor
Service Locator
Un Service Locator es un componente que contiene referencias a los servicios y encapsula la lógica que los localiza dichos servicios. Así, en nuestras clases, utilizamos el Service Locator para obtener instancias de los servicios que realmente necesitamos.
El Service Locator no instancia los servicios. Provee una manera de registrar servicios y mantener una referencia a dichos servicios. Luego de que el servicio es registrado, el Service Locator puede localizarlo.
El Service Locator debe proveer una forma de localizar un servicio sin especificar el tipo. Por ejemplo, puede usar una clave string o el tipo de interface. Esto permite un fácil reemplazo de la dependencia sin modificar el código fuente de la clase.
Es asunto de la implementación de esta forma de IoC, el determinar la forma en que se localizará el servicio requerido.
Inyección de dependencias
Una dependencia entre un componente y otro, puede establecerse estáticamente o en tiempo de compilación, o bien, dinámicamente o en tiempo de ejecución. Es en éste ultimo escenario es donde cabe el concepto de inyección, y para que esto fuera posible, debemos referenciar interfaces y no implementaciones directas.
En general, las dependencias son expresadas en términos de interfaces en lugar de clases concretas. Esto permite un rápido reemplazo de las implementaciones dependientes sin modificar el código fuente de la clase.
Lo que propone entonces la Inyección de dependencias, es no instanciar las dependencias explícitamente en su clase, sino que declarativamente expresarlas en la definición de la clase. La esencia de la inyección de las dependencias es contar con un componente capaz de obtener instancias validas de las dependencias del objeto y pasárselas durante la creación o inicialización del objeto.
1. A necesita una referencia a B, pero no necesita saber como debe crear la instancia de B, solo quiere una referencia a ella.
2. B puede ser una interface, clase abstracta o clase concreta.
3. Antes de que una instancia de A sea usada, necesitara una referencia a una instancia de B.
Aquí no hay un alto acoplamiento entre A y B, ambas pueden cambiar internamente sin afectar a la otra. Los detalles de cómo se crea y gestiona un objeto B, no es decidido en la implementación de A. Es un framework IoC quien usa un método como setB() de A para inyectar luego a un objeto B.
Existen tres principales maneras de implementar la inyección de dependencias:
Inyección basada en Constructor
Las dependencias se inyectan utilizando un constructor con parámetros del objeto dependiente. Éste constructor recibe las dependencias como parámetros y las establece en los atributos del objeto.
Considerando un diseño de dos capas donde tenemos una capa de BusinessFacade y otra de BusinessLogic, la capa BusinessFacade depende de la BusinessLogic para operar correctamente. Todas las clases de lógica de negocio implementan la interface IBusinessLogic.
En la inyección basada en un constructor, se creará una instancia de BusinessFacade usando un constructor parametizado al cual se le pasará una referencia de un IBusinessLogic para poder inyectar la dependencia.
interface IBusinessLogic
{
//Some code
}
class ProductBL implements IBusinessLogic
{
//Some code
}
class CustomerBL implements IBusinessLogic
{
//Some code
}
public class BusinessFacade
{
private IBusinessLogic businessLogic;
public BusinessFacade(IBusinessLogic businessLogic)
{
this.businessLogic = businessLogic;
}
}
El objeto responsable de las dependencias realizará la inyección de la siguiente forma:
IBusinessLogic productBL = new ProductBL();
BusinessFacade businessFacade = new BusinessFacade(productBL);
La principal desventaja de la IoC basada en constructor es que una vez que la clase BusinessFacade es instanciada no podemos cambiar las dependencias inyectadas.
Inyección basada en métodos setters
En este tipo de IoC, se utiliza un método setters para inyectar una dependencia en el objeto que la requiere. Se invoca así al setter de cada dependencia y se le pasa como parámetro una referencia a la misma.
public class BusinessFacade
{
private IBusinessLogic businessLogic;
public setBusinessLogic(IBusinessLogic businessLogic)
{
this.businessLogic = businessLogic;
}
}
El objeto responsable de las dependencias realizará la inyección de la siguiente forma:
IBusinessLogic productBL = new ProductBL();
BusinessFacade businessFacade = new BusinessFacade();
businessFacade.setBusinessLogic(productBL);
La ventaja aquí es que uno puede cambiar la dependencia entre BusinessFacade y la implementación de IBusinessLogic luego de haber instanciado la clase BusinessFacade.
La desventaja es que un objeto con setters no puede ser inmutable y suele ser complicado determinar cuales son las dependencias que se necesitan y en que momento se las necesita. Se recomienda así utilizar IoC basada en constructor a menos que necesite cambiar la dependencia y sepa claramente los momentos en los cuales realizar estos cambios.
Otra desventaja es que al utilizar setters (necesarios para la inyección), estamos exponiendo las propiedades de un objeto al mundo exterior cuando en realidad no era un requerimiento de negocio hacerlo.
Inyección basada en interfaces
Aquí se utiliza una interfaz común que otras clases implementan para poderles luego inyectar dependencias. En el siguiente ejemplo, a toda clase que implemente IBusinessFacade se le podrá inyectar cualquier objeto que implemente IBusinessLogic mediante el método injectBLObject.
interface IBusinessLogic {
//Some code
}
interface IBusinessFacade
{
public void injectBLObject (IBusinessLogic businessLogic);
}
class ProductBL implements IBusinessLogic
{
//Some code
}
class CustomerBL implements IBusinessLogic
{
//Some code
}
class BusinessFacade implements IBusinessFacade
{
private IBusinessLogic businessLogic;
public void injectBLObject (IBusinessLogic businessLogic)
{
this.businessLogic = businessLogic;
}
}
El objeto responsable de las dependencias realizará la inyección de la siguiente forma:
IBusinessLogic businessLogic = new ProductBL(); BusinessFacade businessFacade = new BusinessFacade(); businessFacade.injectBLObject(businessLogic);
Las tres formas de inyección presentadas, pasan una referencia a una implementación de IBusinessLogic más que un tipo particular de BusinessLogic. Esto presenta muchas ventajas, como lo declara Jeremy Weiskotten, un reconocido ingeniero de software de la empresa Kronos.
“Codificar contra interfaces bien definidas es la clave para alcanzar un menor acoplamiento. Acoplando un objeto a una interface en lugar de a una implementación específica, permite utilizar cualquier implementación con el mínimo cambio y riesgo”
Aplicabilidad
Cuando utilizar IoC
La inyección de dependencias no debería usarse siempre que una clase dependa de otra, sino más bien es efectiva en situaciones específicas como las siguientes:
- Inyectar datos de configuración en un componente.
- Inyectar la misma dependencia en varios componentes.
- Inyectar diferentes implementaciones de la misma dependencia.
- Inyectar la misma implementación en varias configuraciones
- Se necesitan alguno de los servicios provistos por un contenedor.
La IoC no es necesaria si uno va a utilizar siempre la misma implementación de una dependencia o la misma configuración, o al menos, no reportará grandes beneficios en estos casos.
Contenedores
En pos de implementar el patrón IoC en alguna de sus variantes, existen los denominados contenedores de inyección de dependencias (CID), que son los componentes encargados de instanciar las dependencias y realizar las inyecciones necesarias entre todos los objetos y además gestionar sus respectivos ciclos de vida.
En cuanto a la instanciación e inyección, un CID se configura para especificarle que dependencias debe instanciar y donde deberá luego inyectarlas. Para la instanciación además, se debe definir el modo de instanciación, es decir, si se creará una nueva siempre que se lo requiera, o se reusará la instancia creada (singleton).
En cuanto a la gestión de los ciclos de vida de los objetos creados, implica que son capaces de administrar las diferentes etapas de la vida del componente (instanciación, configuración, eliminación).
El hecho de que el contenedor a veces mantenga una referencia a los componentes creados luego de la instanciación es lo que lo hace un contenedor.
No todos los objetos deben ser gestionados. El contenedor mantiene una referencia a aquellos objetos que son reusados para futuras inyecciones, como singletones. Cuando configuramos el contenedor para que siempre cree nuevas instancias, entonces éste se suele olvidar de dichos objetos creados, dejando la tarea al GC para recolectarlos luego de que no sean más usados.
Existen varios CID, según el lenguaje de programación que soportan, que modos de IoC soportan, etc.. Para Java tenemos el Butterfly Container, Spring, Pico Container, Guice, y otros.
Conclusión
La natural mutabilidad de las partes o módulos de un sistema, hace que el desarrollar software con mínimo acoplamiento entre sus componentes sea una necesidad mas que un mero requerimiento.
La popularidad y efectividad del patrón IoC en cualquiera de sus formas (Service Locator, Inyección de dependencias, etc.) se observa en la diversidad de frameworks y contenedores disponibles en diversos lenguajes.
En estos contextos, la aplicación del patrón IoC es una de las primeras decisiones que deben tomarse en el diseño de la arquitectura de un producto software.
Saludos
Gastón Guillerón
gguilleron@glnconsultora.com
Referencias
- http://tutorials.jenkov.com/dependency-injection/index.html
- http://geekexplains.blogspot.com/2008/12/spring-ioc-inversion-of-control-di.html
- http://msdn.microsoft.com/en-us/library/cc707904.aspx
- http://www.theserverside.com/news/thread.tss?thread_id=23358
- http://www.devx.com/Java/Article/27583
- http://dotnethitman.spaces.live.com/blog/cns!E149A8B1E1C25B14!267.entry
- http://www.epidataconsulting.com/tikiwiki/tiki-index.php?page=Spring+IoC+Container#Inversion_of_Control
- http://tutorials.jenkov.com/dependency-injection/when-to-use-dependency-injection.html
- http://www.devx.com/dotnet/Article/34066/0/page/2
No hay comentarios:
Publicar un comentario