¿Cuál es la diferencia práctica entre los estilos de inyección de dependencia?

12

Soy nuevo en la inyección de dependencias y tengo algunas preguntas sobre qué estilo debo usar en mis aplicaciones. Acabo de leer Inversion of Control Containers y el patrón de Inyección de dependencias de Martin Fowler, pero no puedo obtener la diferencia práctica entre el constructor, el setter y la inyección de interfaz.

Me parece que las razones para usar uno sobre el otro son solo una cuestión de limpieza y / o claridad del código. ¿Cuál es la diferencia? ¿Existen fuertes ventajas o desventajas de usar uno sobre el otro, o es solo lo que he dicho antes?

En mi opinión, la inyección de constructor es la más intuitiva de todas, así como la inyección de interfaz es la menos. Por otro lado, la inyección de setter es un término medio, pero ¿se supone que puedes cambiar la instancia del objeto de dependencia que inyectaste inicialmente? ¿Este estilo de inyección garantiza que el objeto que necesita la dependencia siempre lo tendrá inyectado? Creo que no, pero corrígeme si me equivoco.

ecampver
fuente
No estoy seguro de qué más has encontrado o leído en el camino. Encontré algunos hilos similares aquí y aquí . Soy nuevo yo mismo, y me gustaría conocer las respuestas que pueda obtener :)
@ user1766760 deberías ayudarme aquí, mi pregunta está siendo votada para ser cerrada, ¡dos votos más y ya está! Vote la pregunta o algo así, no sé qué se puede hacer para evitar el cierre.
ecampver
erm ... Soy nuevo en SO, así que no estoy muy seguro de cómo puedo ayudar / por qué se vota para cerrar (¿No veo ninguna indicación?). Si me atrevo a adivinar, ¿tal vez no sea una pregunta de programación específica sino más bien una discusión?
Es posible que desee ampliar un poco la pregunta. La inyección de dependencia es una forma de "Inversión de control". Hay alternativas Una alternativa útil pero madura para el abuso es la Ubicación del servicio, por ejemplo.
Ian

Respuestas:

12

La inyección de constructor tiene la ventaja de que hace explícita la dependencia y obliga al cliente a proporcionar una instancia. También puede garantizar que el cliente no pueda cambiar la instancia más tarde. Una desventaja (posible) es que debe agregar un parámetro a su constructor.

Setter Injection tiene la ventaja de que no requiere agregar un parámetro al constructor. Tampoco requiere que el cliente configure la instancia. Esto es útil para dependencias opcionales. Esto también puede ser útil si desea que la clase cree, por ejemplo, un repositorio de datos real de forma predeterminada, y luego, en una prueba, puede usar el configurador para reemplazarlo con una instancia de prueba.

La inyección de interfaz , por lo que puedo decir, no es muy diferente a la inyección de setter. En ambos casos, está (opcionalmente) configurando una dependencia que se puede cambiar más adelante.

En última instancia, es una cuestión de preferencia y si se requiere o no una dependencia . Personalmente, uso la inyección de constructor casi exclusivamente. Me gusta que explique las dependencias de una clase al obligar al cliente a proporcionar una instancia en el constructor. También me gusta que el cliente no pueda cambiar la instancia después del hecho.

Muchas veces, mi única razón para pasar dos implementaciones separadas es para probar. En producción, puedo pasar a DataRepository, pero en pruebas, pasaría a FakeDataRepository. En este caso, generalmente proporcionaré dos constructores: uno sin parámetros y otro que acepta a IDataRepository. Luego, en el constructor sin parámetros, encadenaré una llamada al segundo constructor y pasaré a new DataRepository().

Aquí hay un ejemplo en C #:


public class Foo
{
  private readonly IDataRepository dataRepository;

  public Foo() : this(new DataRepository())
  {
  }

  public Foo(IDataRespository dataRepository)
  {
    this.dataRepository = dataRepository;
  }
}

Esto se conoce como inyección de dependencia del pobre. Me gusta porque en el código del cliente de producción, no necesito repetirme al tener varias declaraciones repetidas que parecen

var foo = new Foo(new DataRepository());
Sin embargo, todavía puedo pasar una implementación alternativa para las pruebas. Me doy cuenta de que con la DI de Poor Man estoy codificando mi dependencia, pero eso es aceptable para mí, ya que utilizo principalmente DI para las pruebas.

jhewlett
fuente
gracias, eso está bastante cerca de lo que entiendo, pero aún así, ¿hay algún escenario en el que no puedas usar la inyección del constructor ?
ecampver
1
Probablemente no quiera utilizar la inyección del constructor para dependencias opcionales. No se me ocurren otras razones para no usarlo.
jhewlett
Utilizo la inyección de establecedor de propiedades específicamente para completar algunas clases de configuración que incluyen una gran cantidad de valores. No lo uso en ningún otro lado. Siempre dudo un poco sobre el argumento de los parámetros opcionales porque hay una buena posibilidad de que sea una infracción de la regla de responsabilidad única. Por supuesto, las reglas deben romperse, así que ...
Ian
@jhewlett: eso es fantástico, he estado buscando una buena técnica de troquelado simple para pruebas unitarias que no complique demasiado mi solución, y creo que esto es todo :)
Rocklan
2

Las diferencias entre la inyección del constructor y del setter ya se describieron adecuadamente anteriormente, por lo que no profundizaré más en ellas.

La inyección de interfaz es una forma de inyección más avanzada que es útil porque permite que la dependencia se decida en el momento en que se usa en lugar de durante la inicialización del objeto que lo usará. Esto permite una serie de opciones útiles:

  • La dependencia podría tener un alcance diferente al objeto en el que se inyecta; por ejemplo, puede usar la inyección de interfaz para proporcionar un objeto para el cual existe por sesión de usuario o por hilo a un singleton global. Cada vez que el objeto necesita la dependencia, llamará al método getter proporcionado por el marco, y esto puede devolver resultados diferentes dependiendo de la situación en la que se llame.

  • Permite una inicialización diferida: no es necesario que la dependencia se inicialice hasta que esté a punto de usarse

  • Permite que las dependencias se carguen desde una copia almacenada en caché cuando existen o se reinicializan cuando no existen (por ejemplo, utilizando a SoftReferenceen Java).

Obviamente, las técnicas avanzadas como esta tienen desventajas; en este caso, el problema principal es que el código se vuelve menos claro (las clases utilizadas en su código se vuelven abstractas, y no existe una implementación concreta obvia de ellas, lo que puede ser confuso si no está acostumbrado) y se vuelve más dependiente en su marco de inyección de dependencia (todavía es posible instanciar sus objetos manualmente, por supuesto, pero es más difícil que con otros estilos de inyección).

Jules
fuente