¿Quién le añade los instrumentos a la orquesta?
¿Quién asigna la partitura? ¿A la orquesta o al instrumento?
La inyección de dependencias (DI) no suele hacerse de modo manual (programando una clase que lo haga), sino que se delega en una biblioteca especial (el framework DI) que lo hace, previa configuración.
El framework DI inyecta dependencias de forma universal, no de modo particular a un programa específico y a las clases que lo componen.
En un fichero de configuración orquesta.xml le indicamos los valores inyectables:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "..." ...>
<beans>
<bean id="trompeta"
class="Viento"/>
<bean id="violin"
class="Cuerda"/>
<bean id="tambor"
class="Percusion"/>
<bean id="viola"
class="Cuerda"/>
<bean id="cuarteto"
class="Orquesta">
<property name="instrumento1">
<ref bean="trompeta"/>
</property>
<property name="instrumento2">
<ref bean="violin"/>
</property>
<property name="instrumento3">
<ref bean="viola"/>
</property>
<property name="instrumento4">
<ref bean="tambor"/>
</property>
</bean>
</beans>
La inyección de la dependencia concreta la hace el contenedor (spring en este ejemplo):
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
public class PruebaOrquesta {
public static void main(String[] args) throws Exception {
BeanFactory factory =
new XmlBeanFactory(new FileInputStream("orquesta.xml"));
Orquesta orquesta = (Orquesta)factory.getBean("cuarteto");
for (Instrumento i: orquesta)
orquesta.afinar(i);
orquesta.tocar();
}
}
Un bean es una clase/componente reutilizable en Java que tiene una interfaz bien definida, según una especificación estándar de Java, que permite a un contenedor gestionar su ciclo de vida (crearlos, cambiarles valores de sus propiedades, destruirlos, etc.)
Los beans son usados por muchos frameworks, entre otros Spring:
Más info sobre Spring DI
También se puede inyectar la dependencia en el constructor.
import java.util.logging.Logger;
public class MyClass {
private final static Logger logger;
public MyClass(Logger logger) {
this.logger = logger;
// write an info log message
logger.info("This is a log message.")
}
}
Un contenedor de dependencias en el framework debe responsabilizarse de crear las instancias de Logger e inyectarlas en su sitio (normalmente vía reflexión o introspección)
Otra manera de inyectar dependencias
import org.junit.*;
import static org.junit.Assert.*;
public class OrquestaTest { // no hace falta extends
private Orquesta orquesta;
@Before
protected void setUp() {
Orquesta orquesta = new Orquesta();
orquesta.addInstrumento(new Viento("trompeta"));
orquesta.addInstrumento(new Cuerda("guitarra"));
orquesta.addInstrumento(new Percusion("tambor"));
}
@After
protected void tearDown() {
orquesta = null;
}
@Test
public void testTocar() {
assertNotNull(orquesta.tocar());
assertsEquals(orquesta.tocar(),"GD7CD7G");
}
}
¿Es necesario usar la inyección de dependencias para especificar las partituras con las que deben funcionar los instrumentos de la orquesta?
Supongamos que queremos obtener un listado ordenado por fecha de creación de todas las cuentas bancarias.
¿Cómo afecta este cambio a la versión de BankAccount ya implementada con JDK 1.5?
Resolvemos mediante inyección de dependencias...
BankAcccount.java:
import java.util.*;
import java.io.*;
import java.time.*;
public final class BankAccount implements Comparable<BankAccount> {
private final String id;
private LocalDate creationDate;
private Comparator comparator;
public BankAccount(String number) {
this.id = number;
comparator = new BankAccountComparatorById();
}
public LocalDate getCreationDate() {
return creationDate;
}
public void setCreationDate(LocalDate date) {
this.creationDate = date;
}
public String getId() {
return id;
}
public void setComparator(Comparator cmp) {
comparator = cmp;
}
@Override
public int compareTo(BankAccount other) {
if (this == other)
return 0;
assert this.equals(other) : "compareTo inconsistent with equals.";
return comparator.compare(this, other);
}
@Override
public boolean equals(Object other) {
if (this == other)
return true;
if (!(other instanceof BankAccount))
return false;
BankAccount that = (BankAccount) other;
return this.id.equals(that.getId());
}
@Override
public String toString() {
return id.toString();
}
}
BankAcccountComparatorById.java:
import java.util.Comparator;
class BankAccountComparatorById implements Comparator<BankAccount> {
public int compare(BankAccount o1, BankAccount o2) {
return o1.getId().compareTo(o2.getId());
}
}
BankAcccountComparatorByCreationDate.java:
import java.util.Comparator;
class BankAccountComparatorByCreationDate implements Comparator<BankAccount> {
public int compare(BankAccount o1, BankAccount o2) {
return o1.getCreationDate().compareTo(o2.getCreationDate());
}
}
El motor de inyección de dependencias (por ejemplo, Spring) inyectaría la clase concreta BankAccountComparatorBy... de alguna de estas formas:
Inyección a través del constructor: la clase inyectora suministra la dependencia a través del constructor de la clase dependiente.
Inyección a través de propiedades: la clase inyectora suministra la dependenca a través de un método setter de la clase dependiente.
Inyección a través de métodos (API): la clase inyectora suministra la dependencia a través de una API determinada para la que está preparada (construida/configurada) la clase dependiente.
Ahora podría definirse una anotación del tipo @comparator(BankAccountComparatorById.className) o @compareById que inyecte a BankAccount una dependencia BankAccountComparatorById en BankAccount.comparator.