Estoy tratando de entender las inyecciones de dependencia (DI), y una vez más fallé. Simplemente parece tonto. Mi código nunca es un desastre; Apenas escribo funciones e interfaces virtuales (aunque lo hago una vez en una luna azul) y toda mi configuración se serializa mágicamente en una clase usando json.net (a veces usando un serializador XML).
No entiendo qué problema resuelve. Parece una forma de decir: "hola. Cuando te encuentres con esta función, devuelve un objeto que sea de este tipo y use estos parámetros / datos".
Pero ... ¿por qué usaría eso? Tenga en cuenta que nunca he necesitado usarlo object
también, pero entiendo para qué sirve.
¿Cuáles son algunas situaciones reales en la creación de un sitio web o una aplicación de escritorio donde uno usaría DI? Puedo encontrar casos fácilmente de por qué alguien quiere usar interfaces / funciones virtuales en un juego, pero es extremadamente raro (lo suficientemente raro como para no recordar una sola instancia) usar eso en un código que no sea del juego.
fuente
Respuestas:
Primero, quiero explicar una suposición que hago para esta respuesta. No siempre es cierto, pero con bastante frecuencia:
(En realidad, también hay interfaces que son sustantivos, pero quiero generalizar aquí).
Entonces, por ejemplo, una interfaz puede ser algo como
IDisposable
,IEnumerable
oIPrintable
. Una clase es una implementación real de una o más de estas interfaces:List
oMap
ambas pueden ser implementaciones deIEnumerable
.Para entender el punto: a menudo sus clases dependen unas de otras. Por ejemplo, podría tener una
Database
clase que acceda a su base de datos (¡ja, sorpresa! ;-)), pero también desea que esta clase inicie sesión para acceder a la base de datos. Supongamos que tiene otra claseLogger
, luegoDatabase
tiene una dependencia paraLogger
.Hasta aquí todo bien.
Puede modelar esta dependencia dentro de su
Database
clase con la siguiente línea:Y todo está bien. Está bien hasta el día en que te das cuenta de que necesitas un montón de registradores: a veces quieres iniciar sesión en la consola, a veces en el sistema de archivos, a veces usando TCP / IP y un servidor de registro remoto, y así sucesivamente ...
Y, por supuesto, NO desea cambiar todo su código (mientras tanto, tiene miles de millones) y reemplazar todas las líneas
por:
Primero, esto no es divertido. En segundo lugar, esto es propenso a errores. Tercero, este es un trabajo estúpido y repetitivo para un mono entrenado. Entonces, ¿Qué haces?
Obviamente, es una buena idea introducir una interfaz
ICanLog
(o similar) implementada por todos los distintos registradores. Entonces, el paso 1 en su código es que usted hace:Ahora la inferencia de tipo ya no cambia de tipo, siempre tiene una única interfaz para desarrollar. El siguiente paso es que no desea tener
new Logger()
una y otra vez. Así que pones la confiabilidad para crear nuevas instancias en una sola clase de fábrica central, y obtienes código como:La fábrica misma decide qué tipo de registrador crear. A su código ya no le importa, y si desea cambiar el tipo de registrador que está utilizando, cámbielo una vez : dentro de la fábrica.
Ahora, por supuesto, puede generalizar esta fábrica y hacer que funcione para cualquier tipo:
En algún lugar, este TypeFactory necesita datos de configuración que clase real para instanciar cuando se solicita un tipo de interfaz específico, por lo que necesita una asignación. Por supuesto, puede hacer esta asignación dentro de su código, pero luego un cambio de tipo significa volver a compilar. Pero también podría poner esta asignación dentro de un archivo XML, por ejemplo. Esto le permite cambiar la clase realmente utilizada incluso después del tiempo de compilación (!), Lo que significa dinámicamente, ¡sin recompilar!
Para darle un ejemplo útil de esto: piense en un software que no se registra normalmente, pero cuando su cliente llama y solicita ayuda porque tiene un problema, todo lo que le envía es un archivo de configuración XML actualizado, y ahora tiene registro habilitado, y su soporte puede usar los archivos de registro para ayudar a su cliente.
Y ahora, cuando reemplaza un poco los nombres, termina con una implementación simple de un Localizador de servicios , que es uno de los dos patrones para la Inversión de control (ya que invierte el control sobre quién decide qué clase exacta instanciar).
En general, esto reduce las dependencias en su código, pero ahora todo su código depende del localizador de servicio único central.
La inyección de dependencia es ahora el siguiente paso en esta línea: simplemente elimine esta dependencia única del localizador de servicios: en lugar de que varias clases soliciten al localizador de servicios una implementación para una interfaz específica, usted, una vez más, revierte el control sobre quién instancia qué .
Con la inyección de dependencia, su
Database
clase ahora tiene un constructor que requiere un parámetro de tipoICanLog
:Ahora su base de datos siempre tiene un registrador para usar, pero ya no sabe de dónde proviene este registrador.
Y aquí es donde entra en juego un marco DI: configura sus asignaciones una vez más y luego le pide a su marco DI que cree una instancia de su aplicación por usted. Como la
Application
clase requiere unaICanPersistData
implementación,Database
se inyecta una instancia de , pero para eso primero debe crear una instancia del tipo de registrador configurado paraICanLog
. Y así ...Entonces, para resumir: la inyección de dependencias es una de las dos formas de eliminar dependencias en su código. Es muy útil para los cambios de configuración después del tiempo de compilación, y es una gran cosa para las pruebas unitarias (ya que hace que sea muy fácil inyectar stubs y / o simulacros).
En la práctica, hay cosas que no puede hacer sin un localizador de servicios (por ejemplo, si no sabe de antemano cuántas instancias necesita de una interfaz específica: un marco DI siempre inyecta solo una instancia por parámetro, pero puede llamar un localizador de servicios dentro de un bucle, por supuesto), por lo tanto, con mayor frecuencia cada marco DI también proporciona un localizador de servicios.
Pero básicamente, eso es todo.
PD: Lo que describí aquí es una técnica llamada inyección de constructor , también hay inyección de propiedades donde no hay parámetros de constructor, sino propiedades que se utilizan para definir y resolver dependencias. Piense en la inyección de propiedades como una dependencia opcional, y en la inyección del constructor como dependencias obligatorias. Pero la discusión sobre esto está más allá del alcance de esta pregunta.
fuente
Creo que muchas veces las personas se confunden acerca de la diferencia entre la inyección de dependencia y un marco de inyección de dependencia (o un contenedor como se lo llama a menudo).
La inyección de dependencia es un concepto muy simple. En lugar de este código:
escribes código como este:
Y eso es. Seriamente. Esto te da muchas ventajas. Dos importantes son la capacidad de controlar la funcionalidad desde un lugar central (la
Main()
función) en lugar de distribuirla por todo el programa, y la capacidad de probar más fácilmente cada clase de forma aislada (porque en su lugar puede pasar simulacros u otros objetos falsos a su constructor) de un valor real).El inconveniente, por supuesto, es que ahora tiene una megafunción que conoce todas las clases utilizadas por su programa. Eso es con lo que los marcos DI pueden ayudar. Pero si tiene problemas para comprender por qué este enfoque es valioso, le recomiendo comenzar primero con la inyección de dependencia manual, para que pueda apreciar mejor lo que los diversos marcos pueden hacer por usted.
fuente
Como indicaron las otras respuestas, la inyección de dependencias es una forma de crear sus dependencias fuera de la clase que lo usa. Los inyecta desde el exterior y toma el control sobre su creación lejos del interior de su clase. Esta es también la razón por la cual la inyección de dependencia es una realización del principio de Inversión de control (IoC).
IoC es el principio, donde DI es el patrón. La razón por la que podría "necesitar más de un registrador" nunca se cumple, en lo que respecta a mi experiencia, pero la razón es que realmente lo necesita, cada vez que prueba algo. Un ejemplo:
Mi característica:
Puede probar esto así:
Entonces, en algún lugar del
OfferWeasel
, crea un Objeto de oferta como este:El problema aquí es que es muy probable que esta prueba siempre falle, ya que la fecha que se establece será diferente de la fecha que se afirma, incluso si solo ingresa
DateTime.Now
el código de prueba, podría estar apagado por un par de milisegundos y, por lo tanto, siempre fallan Una mejor solución ahora sería crear una interfaz para esto, que le permita controlar a qué hora se establecerá:La interfaz es la abstracción. Uno es lo REAL, y el otro le permite fingir algún tiempo donde sea necesario. La prueba se puede cambiar así:
Así, aplicaste el principio de "inversión de control", inyectando una dependencia (obteniendo la hora actual). La razón principal para hacer esto es para una prueba de unidad aislada más fácil, hay otras formas de hacerlo. Por ejemplo, una interfaz y una clase aquí son innecesarias ya que en C # las funciones se pueden pasar como variables, por lo que en lugar de una interfaz, puede usar a
Func<DateTime>
para lograr lo mismo. O, si adopta un enfoque dinámico, simplemente pasa cualquier objeto que tenga el método equivalente ( escritura de pato ), y no necesita una interfaz en absoluto.Casi nunca necesitará más de un registrador. No obstante, la inyección de dependencia es esencial para el código tipado estáticamente como Java o C #.
Y ... También debe tenerse en cuenta que un objeto solo puede cumplir adecuadamente su propósito en tiempo de ejecución, si todas sus dependencias están disponibles, por lo que no tiene mucho uso configurar la inyección de propiedades. En mi opinión, todas las dependencias deberían satisfacerse cuando se llama al constructor, por lo que la inyección de constructor es lo que debe elegir.
Espero que haya ayudado.
fuente
Creo que la respuesta clásica es crear una aplicación más desacoplada, que no tiene conocimiento de qué implementación se utilizará durante el tiempo de ejecución.
Por ejemplo, somos un proveedor de pagos central, trabajando con muchos proveedores de pagos en todo el mundo. Sin embargo, cuando se realiza una solicitud, no tengo idea de a qué procesador de pagos voy a llamar. Podría programar una clase con un montón de casos de interruptores, como:
Ahora imagine que ahora necesitará mantener todo este código en una sola clase porque no está desacoplado correctamente, puede imaginar que por cada nuevo procesador que admitirá, necesitará crear un nuevo caso de // switch para En cada método, esto solo se vuelve más complicado, sin embargo, al usar la Inyección de dependencia (o Inversión de control, como a veces se le llama, lo que significa que quien controla el funcionamiento del programa se conoce solo en tiempo de ejecución, y no complicación), puede lograr algo Muy ordenado y mantenible.
** El código no se compilará, lo sé :)
fuente
new ThatProcessor()
lugar de utilizar un marcoLa razón principal para usar DI es que desea poner la responsabilidad del conocimiento de la implementación donde está el conocimiento. La idea de DI está muy en línea con la encapsulación y el diseño por interfaz. Si el front-end solicita algunos datos al back-end, entonces no es importante para el front-end cómo el back-end resuelve esa pregunta. Eso depende del manejador de solicitudes.
Eso ya es común en OOP durante mucho tiempo. Muchas veces creando piezas de código como:
El inconveniente es que la clase de implementación todavía está codificada, por lo tanto, tiene el conocimiento de qué implementación se usa. DI lleva el diseño por interfaz un paso más allá, que lo único que el front end necesita saber es el conocimiento de la interfaz. Entre el DYI y el DI está el patrón de un localizador de servicios, porque el front end tiene que proporcionar una clave (presente en el registro del localizador de servicios) para permitir que su solicitud se resuelva. Ejemplo de localizador de servicio:
DI ejemplo:
Uno de los requisitos de DI es que el contenedor debe poder averiguar qué clase es la implementación de qué interfaz. Por lo tanto, un contenedor DI requiere un diseño fuertemente tipado y solo una implementación para cada interfaz al mismo tiempo. Si necesita más implementaciones de una interfaz al mismo tiempo (como una calculadora), necesita el localizador de servicios o el patrón de diseño de fábrica.
D (b) I: Inyección de dependencia y diseño por interfaz. Sin embargo, esta restricción no es un gran problema práctico. El beneficio de usar D (b) I es que sirve de comunicación entre el cliente y el proveedor. Una interfaz es una perspectiva sobre un objeto o un conjunto de comportamientos. Lo último es crucial aquí.
Prefiero la administración de contratos de servicio junto con D (b) I en la codificación. Deberían ir juntos. El uso de D (b) I como una solución técnica sin la administración organizacional de los contratos de servicios no es muy beneficioso en mi punto de vista, porque DI es entonces solo una capa adicional de encapsulación. Pero cuando puede usarlo junto con la administración de la organización, realmente puede hacer uso del principio de organización que D (b) I ofrece. A la larga, puede ayudarlo a estructurar la comunicación con el cliente y otros departamentos técnicos en temas como pruebas, versiones y desarrollo de alternativas. Cuando tienes una interfaz implícita como en una clase codificada, entonces es mucho menos comunicable con el tiempo que cuando la haces explícita usando D (b) I. Todo se reduce al mantenimiento, que se realiza con el tiempo y no a la vez. :-)
fuente