Primero, quiero explicar una suposición que hago para esta respuesta. No siempre es cierto, pero con bastante frecuencia:
Las interfaces son adjetivos; las clases son sustantivos
(En realidad, también hay interfaces que son sustantivos, pero quiero generalizar aquí).
Entonces, por ejemplo, una interfaz puede ser algo como IDisposable
, IEnumerable
o IPrintable
. Una clase es una implementación real de una o más de estas interfaces: List
o Map
ambas pueden ser implementaciones de IEnumerable
.
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 clase Logger
, luego Database
tiene una dependencia para Logger
.
Hasta aquí todo bien.
Puede modelar esta dependencia dentro de su Database
clase con la siguiente línea:
var logger = new Logger();
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
var logger = new Logger();
por:
var logger = new TcpLogger();
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:
ICanLog logger = new Logger();
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:
ICanLog logger = LoggerFactory.Create();
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:
ICanLog logger = TypeFactory.Create<ICanLog>();
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 tipo ICanLog
:
public Database(ICanLog logger) { ... }
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 una ICanPersistData
implementación, Database
se inyecta una instancia de , pero para eso primero debe crear una instancia del tipo de registrador configurado para ICanLog
. 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.