lunes, 21 de julio de 2014

Inyección de dependencias (DI)

Inyeccion de dependencias (DI)

Ver más
Spring's dependency injection API

Inyección de dependencias

En la JSR 330 se especifican formas para la obtención de objetos de manera que se maximice la reusabilidad, la testabilidad y la mantenibilidad comparado con las aproximaciones tradicionales tales como constructores, factorias y service locators (ej. JNDI). Este proceso conocido como Inyección de Dependencias es beneficioso para un numero extenso de aplicaciones no triviales.

Muchos tipos dependen de otros. "Favorecer la composición a la herencia". De esta manera Dispositivo puede depender de Posicion y este a su vez de Coordenadas. El proceso de hallar una instancia para resolver una dependencia en tiempo de ejecución se conoce como resolución de dependencia. Si no es posible encontrar dicha instancia, la dependencia se considera no resuelta y la aplicación queda inconsistente.

En ausencia de inyección de dependencias, un objeto puede resolver sus dependencias de ciertas maneras. Se puede invocar un constructor, asignar en duro un objeto existente directamente a la implementación de la dependencia y su ciclo de vida.

class Device { Position position; Device(){ this.position = new GeoPosition(); } } class GeoPosition { Coords coords; GeoPosition() { this.coords = new GeoCoords(); } }

Si es necesaria mayor flexibilidad, el objeto puede invocar a una factoria o a un service locator: class GeoPosition {     Coords coords;     GeoPosition () {         this.coords = CoordsSourceFactory.getInstance(this.getClass().getName());     } }

Elegir entre uno de estos dos aproximaciones para la resolución de dependencias, el programador debe . A partir de un constructor simplificamos pero perdemos flexibilidad. Una factoria aporta desacoplamiento entre el tipo de cliente y su implementación pero requiere aumentar el código. Un service locator aportan aún mayor desacoplamiento pero disminuye que dichas dependencias sean type safe en tiempo de compilación. Las tres soluciones afectan a la hora de realizar test unitarios. For ejemplo, si el programador utiliza una factoria, cada test realizado sobre el código que depende de la factoria tendrá que simular dicha factoria y restablecer a continuación las modificaciones para evitar efectos colaterales: void testGeoPosition() {     Coords original = CoordsSourceFactory.getInstance("GeoPosition.class");     CoordsSourceFactory.setInstance(new MockGeoCoordsSource());     try {         // test recoger coordenadas         Postion pos = new GeoPosition();         ...     } finally { CoordsSourceFactory.setInstance((GeoCoords) original);     } }

En la práctica, la realización de este tipo de test generan mayor cantidad de código redundante, el hecho de "utilizar un objeto mock" y "restablecer el estado inicial" para cada una de las dependencias hace poco mantenible el código. El programador debe además predecir de forma exacta cuanta flexibilidad es necesaria o alguien sufrira las consecuencias. Si el programador elige primeramente utilizar constructores pero mas adelante es necesaria mayor flexibilidad, se deben reescribir cada llamada a un constructor. Realizar dicha modificación, crea código redundante, complejidad y riesgo de error.

La Inyección de Depencias (DI), trata estos problemas. En lugar de que sea tarea del programador la llamada a un constructor o factoria. En vez de ser el programador el encargado de realizar la llamada a un constructor o factoria, esto se realiza automáticamente a través de una herramienta llamada inyector de dependencias. class Device { Position position; @Inject Device(GeoPosition position){ this.position = position; } }

El inyector de dependencias va inyectando las dependencias a las dependencias anidadas hasta construir completamente el objeto. Por ejemplo, supongamos que el programador indica al inyector de dependencias que cree una instancia Device: class Device { Position position;     @Inject Device(GeoPosition position) { ... }     ... }

El inyector de dependencias podria hacer lo siguiente (Device->Position->Coord): 1. Buscaría una GeoCoord 2. Construir una GeoPosition inyectando GeoCoord 3. Construir un objeto Device inyectando GeoPosition

Esto permite que el código sea más claro, flexible y relativamente libre del manejo de dependencias. En los test unitarios, el programador puede ahora construir objetos directamente (sin ayuda del inyector) y asignar dependencias a objetos mock. Ya no es necesario configurar y restablecer factorias o service locators para cada test. Esto lo simplifica enormemente. void testDevice() {     Device dev = new Device(new MockGeoPosition());      ... }

La disminución total en la complejidad de los test unitarios es proporcional al producto entre el numero de test y el numero de dependencias. El programador indica mediante la annotation @Inject en constructores, métodos y propiedades su inyectabilidad. El inyector de dependencias identifica la clase de dependencia a través de esta annotation e inyecta la dependencia en tiempo de ejecución. Además, el inyector de dependencias puede comprobar que todas las dependencias han sido satisfechas en tiempo de compilación. Un service locator, por el contrario no puede detectar dependencias no satisfechas hasta la ejecución.

Se debe configurar un inyector de dependencias para indicar que es lo que debe inyectarse. Existen diferentes tipos de configuración que se aplican en diferentes contextos. Una aproximación es buscar en el classpath las implementaciones de las dependencias, esto evita al programador escribir código de forma explicita. Esta aproximación puede ser útil en la elaboración de prototipos. Para una aplicación más amplia y de larga duración seria preferible una aproximación más explicita y mantenible. Por ejemplo, se podria escribir XML que indica al inyector de dependencias inyectar un EJB cuando una clase necesite un objeto GeoCoords: ...

A menudo tiene sentido usar mss de un mecanismo de configuración en la misma aplicación. Por ejemplo una primera configuración en la fase de prototipo puede evolucionar a una aplicación real de manera que sea deseable migrar a una configuración más mantenible. Otro caso, es en el que se establecen algunos de los recursos de forma explícita, mientras otros se configuran de manera automatica a través de ficheros XML o DB.

Ver más
Spring's dependency injection API