Inyección de dependencias. ¿Qué es y para qué sirve?

La inyección de dependencias (DI) es un patrón de diseño que deriva de un patrón más genérico llamado Inversión de Control. DI hace uso de la modularidad y la reutilización, las cuales siempre deberíamos tener en cuenta si nuestra aplicación va a estar dotada de mayor funcionalidad. Más adelante, veremos en que consiste la Inversión de Control, pero hoy vamos a centrarnos en la DI, visto además de una manera simplista, con un ejemplo sencillo.

En el ejemplo, vamos a ver la representación de una puerta con cerradura de llave y una puerta con cerradura de código. Esta sería la representación sin el uso de DI:

Start.java
[crayon]
public class Start {

public static void main(String[] args) {
//Usamos una puerta con cerradura de llave
ObjetoPuertaLlave puertaLlave = new ObjetoPuertaLlave();
puertaLlave.abrir();

//Usamos una puerta con cerradura de codigo
ObjetoPuertaCodigo puertaCodigo = new ObjetoPuertaCodigo();
puertaCodigo.abrir();
}
}
[/crayon]

Esta es la manera tradicional de hacerlo, se crea un nuevo objeto puerta que tenga una cerradura de tipo llave por un lado y otro que tenga una cerradura de tipo código por el otro.

ObjetoPuertaLlave.java:
[crayon]
public class ObjetoPuertaLlave {

private ObjetoCerraduraLlave cerradura;

public ObjetoPuertaLlave(){
cerradura = new ObjetoCerraduraLlave();
}

public void abrir(){
cerradura.accionar();
}
}
[/crayon]

ObjetoCerraduraLlave.java:
[crayon]
public class ObjetoCerraduraLlave{

public void accionar() {
System.out.println(“Cerradura con Llave”);
}
}
[/crayon]

ObjetoPuertaCodigo.java:
[crayon]
public class ObjetoPuertaCodigo {

private ObjetoCerraduraCodigo cerradura;

public ObjetoPuertaCodigo(){
cerradura = new ObjetoCerraduraCodigo();
}

public void abrir(){
cerradura.accionar();
}
}
[/crayon]

ObjetoCerraduraCodigo.java:
[crayon]
public class ObjetoCerraduraCodigo{
public void accionar() {
System.out.println(“Cerradura con Código”);
}
}
[/crayon]

La salida por consola de la ejecución es la siguiente:

Cerradura con Llave
Cerradura con Codigo

Obviamente esta no es la mejor manera de implementar esta representación. Ahora veremos como usar DI para hacer esto mismo pero más reutilizable, ahora sólo tendremos una clase puerta:

ObjetoPuerta.java:
[crayon]
public class ObjetoPuerta {

private CerraduraInterface cerradura;

public ObjetoPuerta(CerraduraInterface cerraduraGeneric){
cerradura = cerraduraGeneric;
}

public void usar(){
cerradura.accionar();
}
}
[/crayon]

Vemos como esta clase puerta es más genérica y consta de un atributo que en realidad es una interface. Ésto es básicamente la inyección de dependencias, en la que en vez de invocar instancias nuevas de objetos desde la propia clase, se invocan desde fuera, siendo pasadas como parámetros. De esta manera, este ObjetoPuerta nos servirá para representar cualquier puerta que tenga una cerradura siempre y cuando ese objeto cerradura implemente la interface que contiene ObjetoPuerta como atributo.

CerraduraInterface.java:
[crayon]
public interface CerraduraInterface {
void accionar();
}
[/crayon]

ObjetoCerraduraLlave.java:
[crayon]
public class ObjetoCerraduraLlave implements CerraduraInterface{
@Override
public void accionar() {
System.out.println(“Cerradura con Llave”);
}
}
[/crayon]

ObjetoCerraduraCodigo.java:
[crayon]
public class ObjetoCerraduraCodigo implements CerraduraInterface{

@Override
public void accionar() {
System.out.println(“Cerradura con Código”);
}
}
[/crayon]

Start.java:
[crayon]
public class Start {

public static void main(String[] args) {
ObjetoCerraduraLlave llave =new ObjetoCerraduraLlave();
ObjetoPuerta puerta = new ObjetoPuerta(llave);
puerta.usar();

ObjetoCerraduraCodigo codigo = new ObjetoCerraduraCodigo();
puerta = new ObjetoPuerta(codigo);
puerta.usar();
}
}
[/crayon]

La salida de la ejecución es la siguiente:

Cerradura con Llave
Cerradura con Código

Como se puede ver, la salida es la misma en ambos casos, la diferencia es que si quisiéramos añadir una puerta con cerradura de tipo “Reconocimiento_dactilar“, en el primer caso, tendríamos que crear un nuevo tipo de puerta que tuviera como atributo un objeto de tipo CerraduraDactilar y ya tendríamos tres objetos distintos cuya única diferencia es el tipo de un atributo.

En el segundo caso, usando DI, bastaría con crear el objeto CerraduraDactilar y pasarla como parámetro al constructor de ObjetoPuerta. Seguiríamos teniendo un solo ObjetoPuerta y solamente añadiríamos la nueva implementación de la cerradura.

Uno de los problemas que plantea el uso de interfaces es que a la hora de depurar visualmente el código no vemos de que tipo es el objeto que pasamos, ya que solo vemos su representación como interface, nada que haciendo Debug en nuestro IDE no se solucione.

Es cierto que podríamos haber creado un ObjetoPuerta genérico del que heredasen todos los demás objetosPuerta con sus atributos cerradura concretos, pero no resolveríamos en nada el problema de tener objetos iguales cuyas únicas diferencias son un atributo.

Alguien puede preguntarse la utilidad real de este patrón teniendo en cuenta que pudiera ser que tuviéramos un ObjetoPuerta que tuviera dos o más atributos o un ObjetoCerradura que tuviera algún atributo y volvería a presentarse el problema inicial, creo dos objetos de tipo CerraduraConAtributo y CerraduraSinAtributo o vuelvo a usar DI.

Pero aún usando DI, a la fuerza tendremos que instanciar los dos tipos de cerradura y pasar como parámetro al ObjetoPuerta la que nos convenga en el momento, tal y como hemos hecho con ObjetoCerraduraCodigo y ObjetoCerraduraLlave.

Para solventar todo esto existe la inversión de control (IoC) donde veremos como mediante la inyección de dependencias y la reflexión se irán creando las instancias de clases que necesitemos según el momento y necesidad trasladando todo el control fuera de la clase principal.

Enlaces|
¿Qué es la inyección de dependencias?
 

<O,_,O>