Principio de Inversión de Dependencias (DIP)
Definición
El Principio de Inversión de Dependencias establece que:
“Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones.”
“Las abstracciones no deben depender de los detalles. Los detalles deben depender de las abstracciones.”
— Robert C. Martin (Uncle Bob)
En términos simples:
No dependas de clases concretas.
Depende de interfaces o abstracciones.
Inyecta las dependencias desde afuera.
Invierte la dirección de las dependencias.
Analogía del Enchufe
Sin el Principio (acoplamiento directo)
Imagina una laptop cuyo cable de corriente está soldado internamente:
Si el cable se daña, debe reemplazarse toda la laptop.
No permite usar diferentes voltajes.
Es difícil de adaptar para otros países.
Con el Principio (desacoplamiento)
Ahora imagina una laptop con un puerto estándar:
Permite conectar diferentes cables.
Cambiar el cable no afecta a la laptop.
Puede funcionar en cualquier país usando un adaptador adecuado.
Conclusión:
El puerto representa la abstracción y los cables son las implementaciones. La laptop no depende de un cable específico, sino de un estándar.
El Problema: Dependencia Directa
Ejemplo
// Clase de bajo nivel (detalle concreto)
public class EmailService
{
public void EnviarEmail(string mensaje)
{
Console.WriteLine($"Enviando email: {mensaje}");
}
}
// Clase de alto nivel dependiente de una clase concreta
public class NotificacionService
{
private EmailService _emailService = new EmailService();
public void Notificar(string mensaje)
{
_emailService.EnviarEmail(mensaje);
}
}
// Uso del sistema
var notificador = new NotificacionService();
notificador.Notificar("Hola mundo"); // Solo puede enviar emails
Problemas
Acoplamiento fuerte: NotificacionService depende directamente de EmailService.
Difícil de extender: No puede usarse SMS u otro canal sin modificar la clase.
Difícil de testear: No se pueden simular dependencias fácilmente.
Viola el Principio Abierto/Cerrado: No está cerrada a modificaciones.
La Solución: Inversión de Dependencias
Paso 1: Crear una Abstracción
public interface INotificacion
{
void Enviar(string mensaje);
}
Paso 2: Implementar las Clases Concretas
public class EmailService : INotificacion
{
public void Enviar(string mensaje)
{
Console.WriteLine($"Email enviado: {mensaje}");
}
}
public class SMSService : INotificacion
{
public void Enviar(string mensaje)
{
Console.WriteLine($"SMS enviado: {mensaje}");
}
}
public class WhatsAppService : INotificacion
{
public void Enviar(string mensaje)
{
Console.WriteLine($"WhatsApp enviado: {mensaje}");
}
}
Paso 3: Inyectar la Dependencia
public class NotificacionService
{
private readonly INotificacion _canal;
// Inyección de dependencia por constructor
public NotificacionService(INotificacion canal)
{
_canal = canal;
}
public void Notificar(string mensaje)
{
_canal.Enviar(mensaje);
}
}
Paso 4: Uso del Sistema
var notificadorEmail = new NotificacionService(new EmailService());
notificadorEmail.Notificar("Hola por Email");
var notificadorSMS = new NotificacionService(new SMSService());
notificadorSMS.Notificar("Hola por SMS");
var notificadorWhatsApp = new NotificacionService(new WhatsAppService());
notificadorWhatsApp.Notificar("Hola por WhatsApp");
Ventaja principal:
El comportamiento puede cambiarse sin modificar la clase NotificacionService. Solo se inyecta una nueva implementación de la interfaz.
Tipos de Inyección de Dependencias
- Inyección por Constructor (recomendada)
public class ServicioUsuario
{
private readonly IRepositorio _repositorio;
public ServicioUsuario(IRepositorio repositorio)
{
_repositorio = repositorio ?? throw new ArgumentNullException();
}
}
Ventajas:
Las dependencias son obligatorias desde la creación del objeto.
Las propiedades pueden mantenerse inmutables (readonly).
Facilita la realización de pruebas unitarias (mocking).
Mejora la claridad del código al mostrar explícitamente qué necesita la clase.
Top comments (0)