Fundamentos de Inyección de dependencias
Parte de la sección Programación Orientada a Objetos del Journey de C# de Coddy. Lección 46 de 70.
Inyección de dependencias (DI) es una técnica en la que una clase recibe sus dependencias desde el exterior en lugar de crearlas por sí misma. Esto hace que el código sea más flexible, comprobable y débilmente acoplado.
Sin DI, una clase crea sus propias dependencias, lo que dificulta cambiarlas o probarlas:
public class OrderService
{
private EmailSender sender = new EmailSender(); // Fuertemente acoplado
public void PlaceOrder() => sender.Send("Order placed");
}Con DI, las dependencias se pasan a través del constructor:
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message) => Console.WriteLine("Email: " + message);
}
public class OrderService
{
private IMessageSender sender;
public OrderService(IMessageSender sender)
{
this.sender = sender;
}
public void PlaceOrder() => sender.Send("Order placed");
}Ahora OrderService no sabe ni le importa si está usando correo electrónico, SMS o un remitente simulado para las pruebas. La dependencia se «inyecta» cuando se crea el objeto:
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // Email: Pedido realizadoEste patrón sigue el principio de depender de abstracciones (interfaces) en lugar de implementaciones concretas. Permite intercambiar implementaciones sin modificar la clase que las utiliza, lo cual es esencial para las pruebas unitarias y para mantener arquitecturas flexibles.
Desafío
FácilConstruyamos un sistema de registro que demuestre el poder de la inyección de dependencias. En lugar de codificar directamente cómo se escriben los registros, crearás una clase flexible Logger que pueda trabajar con cualquier destino de registro —consola, simulación de archivo o cualquier otra cosa— sin conocer los detalles específicos.
Organizarás tu código en tres archivos:
ILogWriter.cs: Define una interfaz llamadaILogWriteren el espacio de nombresLogging. Esta interfaz representa el contrato para cualquier destino de registro y debe tener un único métodoWrite(string message)que devuelva una cadena que describa dónde se escribió el mensaje.LogWriters.cs: Crea dos implementaciones concretas deILogWriteren el espacio de nombresLogging:ConsoleLogWriter- su métodoWritedevuelveConsole: {message}FileLogWriter- recibe un nombre de archivo mediante su constructor y su métodoWritedevuelveFile[{filename}]: {message}
Logger.cs: Crea una claseLoggeren el espacio de nombresLoggingque reciba su dependencia mediante la inyección en el constructor.Loggerdebe:- Aceptar un
ILogWritermediante su constructor y almacenarlo en un campo privado - Tener un método
Log(string message)que delegue en el escritor inyectado y devuelva el resultado
- Aceptar un
Program.cs: Demuestra la inyección de dependencias creando dos registradores diferentes: uno con unConsoleLogWritery otro con unFileLogWriter. Muestra cómo la misma claseLoggerse comporta de manera diferente según la dependencia que se inyecte.
Recibirás dos entradas:
- El nombre de archivo para el registrador de archivos
- El mensaje que se registrará
Crea un Logger con un ConsoleLogWriter y registra el mensaje. Después crea otro Logger con un FileLogWriter (usando el nombre de archivo de la entrada) y registra el mismo mensaje. Imprime cada resultado en su propia línea.
Por ejemplo, si las entradas son app.log y System started, la salida debería ser:
Console: System started
File[app.log]: System startedObserva cómo la clase Logger nunca cambia: funciona con cualquier implementación de ILogWriter. Esta es la esencia de la inyección de dependencias: Logger depende de una abstracción (la interfaz), y la implementación concreta se "inyecta" desde fuera. ¡Más adelante podrías añadir fácilmente un DatabaseLogWriter o un CloudLogWriter sin modificar en absoluto la clase Logger!
Pruébalo tú mismo
using System;
using Logging;
class Program
{
public static void Main(string[] args)
{
// Lee las entradas
string filename = Console.ReadLine();
string message = Console.ReadLine();
// TODO: Crea un Logger con un ConsoleLogWriter y registra el mensaje
// Imprime el resultado
// TODO: Crea otro Logger con un FileLogWriter (usando el filename) y registra el mismo mensaje
// Imprime el resultado
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosNamespaces y directivasIntro a clases y objetosLa palabra clave 'this'Métodos y parámetrosCampos vs PropiedadesConstructoresInicializadores de objetosResumen - Calculadora simple4Herencia
Sintaxis básica de herencia (:)La palabra clave 'base'Palabras clave Virtual y OverrideClases selladas (Sealed)La clase base 'object'Resumen: Jerarquía de empleados7Características avanzadas
Sobrecarga de operadoresIndexadores (this[])Sobrescritura de ToString()Métodos de extensiónResumen - Lista personalizada10Patrones de diseño - Parte 1
Introducción a los patrones de diseñoSingleton Thread-SafePatrón FactoryPatrón Observer (Eventos)Patrón Strategy2Propiedades y miembros estáticos
Propiedades autoimplementadasPropiedades de solo lectura y escrituraCampos y métodos estáticosClases estáticasMiembros con cuerpo de expresión5Polimorfismo e Interfaces
Polimorfismo: Compilación vs. EjecuciónInterfaz vs. Clase AbstractaInterfaces MúltiplesInterfaces ExplícitasUpcasting y DowncastingRepaso: Calculadora de Figuras8Conceptos avanzados de POO
Composición sobre herenciaGenéricos (clases y métodos)Delegados y eventosAtributos y ReflexiónIDisposable y la sentencia usingFundamentos de Inyección de dependencias11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Arquitectura de clases
Datos de instancia vs. estáticosPalabras clave 'readonly' y 'const'Campos de respaldoResumen - Gestor de cuentas bancarias6Encapsulamiento
Modificadores de accesoPropiedades para el encapsulamientoImplementación de ocultamiento de datosPatrones de inmutabilidadRepaso - Registros de estudiantesPractica por tu cuenta: Compilador de C# online