Dependencia de inyección se refiere al patrón de decirle a una clase cuáles serán sus dependencias, en lugar de requerir que la clase sepa dónde encontrar todas sus dependencias.
Así, por ejemplo, que van desde la siguiente:
public class UserFetcher {
private final DbConnection conn =
new DbConnection("10.167.1.25", "username", "password");
public List<User> getUsers() {
return conn.fetch(...);
}
}
a algo como esto:
public class UserFetcher {
private final DbConnection conn;
public UserFetcher(DbConnection conn) {
this.conn = conn;
}
public List<User> getUsers() {
return conn.fetch(...);
}
}
Esto reduce el acoplamiento en el código, que es especialmente útil si quieres prueba de la unidad UserFetcher
. Ahora, en lugar de UserFetcher
siempre ejecutándose en una base de datos que se encuentra en 10.167.1.25
, puede pasar un DbConnection
a una base de datos de prueba. O, incluso más útil en una prueba rápida, puede pasar una implementación o subclase de DbConnection
que ni siquiera se conecta a una base de datos, ¡simplemente descarta las solicitudes!
Sin embargo, este tipo de inyección de dependencias primitiva hace cableado (que proporciona un objeto con sus dependencias) más difícil, porque se ha sustituido el acceso a la dependencia mediante una variable global (o un objeto de instancia local) con el paso de la dependencia a través de todo el gráfico de objetos.
Piense en un caso en el que UserFetcher
es una dependencia de AccountManager
, que es una dependencia de AdminConsole
. Entonces AdminConsole
necesidades pasan a la instancia DbConnection
a AccountManager
y AccountManager
tiene que pasar a UserFetcher
... aunque ninguno de ellos, ni AdminConsole
AccountManager
necesidad de utilizar el DbConnection
directamente!
Un inversión de control contenedor (primavera, Guice, etc) que tiene como objetivo hacer más fácil la inyección de dependencias conectando automáticamente (siempre) las dependencias. Para hacer esto, le dice a su contenedor IoC una vez cómo proporcionar un objeto (en Spring, esto se llama bean) y siempre que otro objeto solicite esa dependencia, será provisto por el contenedor.
Así que nuestro último ejemplo podría tener este aspecto con Guice, si usamos la inyección de constructor:
public class UserFetcher {
private final DbConnection conn;
@Inject //or @Autowired for Spring
public UserFetcher(DbConnection conn) {
this.conn = conn;
}
public List<User> getUsers() {
return conn.fetch(...);
}
}
y tenemos que configurar el contenedor IoC. En Guice esto se hace a través de una implementación de Module
; en Spring configura un contexto de aplicación , a menudo a través de XML.
public class MyGuiceModule extends AbstractModule {
@Override
public void configure() {
bind(DbConnection.class).toInstance(
new DbConnection("localhost", "username", "password"));
}
}
Ahora, cuando UserFetcher
se construye mediante Guice o primavera, la DbConnection
se proporciona automáticamente.
Guice tiene a really good Wiki article sobre la motivación detrás de la inyección de dependencia y el uso adicional de un contenedor IoC. Vale la pena leer todo el camino.
El patrón estrategia es sólo un caso especial de inyección de dependencias, donde se inyecta lógica en lugar de un objeto (a pesar de que en Java, la lógica se encapsula en un objeto). Es una forma de desacoplar la lógica empresarial independiente.
Por ejemplo, es posible que tenga un código como éste:
public Currency computeTotal(List<Product> products) {
Currency beforeTax = computeBeforeTax(products);
Currency afterTax = beforeTax.times(1.10);
}
Pero lo que si se quería extender este código para una nueva jurisdicción, con un esquema de impuesto sobre las ventas diferente? Se puede inyectar la lógica para calcular el impuesto, como este:
public interface TaxScheme {
public Currency applyTax(Currency beforeTax);
}
public class TenPercentTax implements TaxScheme {
public Currency applyTax(Currency beforeTax) {
return beforeTax.times(1.10);
}
}
public Currency computeTotal(List<Product> products, TaxScheme taxScheme) {
Currency beforeTax = computeBeforeTax(products);
Currency afterTax = taxScheme.applyTax(beforeTax);
return afterTax;
}