Fundamentos de Inyección de Dependencias en C#
Aprende los fundamentos de Inyección de Dependencias en C#, gestionando dependencias y logrando bajo acoplamiento.
En el desarrollo de software, la Inyección de Dependencias (DI) es un patrón de diseño importante utilizado para gestionar dependencias. Con DI, las clases reciben los objetos que necesitan desde el exterior en lugar de crearlos internamente. Esto hace que el código sea más flexible, comprobable y mantenible. En el ecosistema de C# y .NET Core, la DI está soportada de forma nativa.
¿Qué es una dependencia?
Una clase depende de otra cuando necesita su funcionalidad.
Por ejemplo, un OrderService puede necesitar una instancia de ILogger al crear un pedido.
Si OrderService usa directamente new Logger(), queda fuertemente acoplado a la clase Logger.
public class OrderService
{
private readonly Logger _logger = new Logger();
public void CreateOrder(string product)
{
_logger.Log($"Pedido creado: {product}");
}
}
public class Logger
{
public void Log(string message) => Console.WriteLine(message);
}
En este enfoque, el Logger no puede ser reemplazado.
Si desea usar otro sistema de registro, debe modificar el código de OrderService.
Aquí es donde entra en juego la Inyección de Dependencias.
Solución con Inyección de Dependencias
Con DI, las dependencias se abstraen y se proporcionan desde el exterior (a través del constructor o de parámetros de métodos).
De esta forma, OrderService depende de la interfaz ILogger, y el mundo exterior decide qué logger usar.
public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message) => Console.WriteLine("[Consola] " + message);
}
public class FileLogger : ILogger
{
public void Log(string message) =>
System.IO.File.AppendAllText("log.txt", message + "\n");
}
public class OrderService
{
private readonly ILogger _logger;
// Inyección por constructor
public OrderService(ILogger logger)
{
_logger = logger;
}
public void CreateOrder(string product)
{
_logger.Log($"Pedido creado: {product}");
}
}
class Program
{
static void Main()
{
// Se puede elegir un logger diferente
ILogger logger = new ConsoleLogger();
var service = new OrderService(logger);
service.CreateOrder("Portátil");
}
}
En este ejemplo, OrderService ya no está vinculado a una clase Logger específica;
solo depende de la interfaz ILogger.
Por lo tanto, ConsoleLogger o FileLogger pueden utilizarse fácilmente.
Tipos de Inyección de Dependencias
- Inyección por constructor: Las dependencias se proporcionan a través del constructor (el método más común).
- Inyección por propiedad: Las dependencias se asignan a través de propiedades públicas.
- Inyección por método: Las dependencias se pasan mediante parámetros de método.
Uso de DI en .NET Core
En las aplicaciones .NET Core, se dispone de un contenedor de DI integrado.
Los servicios se registran en Program.cs o Startup.cs, y se inyectan automáticamente en las clases que los necesiten.
var builder = WebApplication.CreateBuilder(args);
// Registrar servicios
builder.Services.AddScoped<ILogger, ConsoleLogger>();
builder.Services.AddScoped<OrderService>();
var app = builder.Build();
app.MapGet("/order", (OrderService service) =>
{
service.CreateOrder("Teléfono");
return "Pedido procesado.";
});
app.Run();
Aquí, el contenedor DI crea automáticamente una instancia de ConsoleLogger para OrderService y la inyecta.
Ventajas
- Bajo acoplamiento: Las clases dependen de interfaces en lugar de implementaciones concretas.
- Testabilidad: Las pruebas unitarias son más fáciles con mocks u objetos simulados.
- Flexibilidad: Se pueden intercambiar fácilmente diferentes implementaciones.
- Mantenibilidad: Las dependencias se gestionan de forma centralizada, mejorando la legibilidad del código.
TL;DR
- Inyección de Dependencias: Las clases reciben los objetos que necesitan desde el exterior.
- Inyección por constructor: El enfoque más común; las dependencias se pasan a través del constructor.
- .NET Core: Proporciona un contenedor de DI integrado.
- Beneficios: Permite una arquitectura más flexible, comprobable y fácil de mantener.
Artículos relacionados
Clases, Objetos, Propiedades y Métodos en C#
Aprende cómo las clases, objetos, propiedades y métodos en C# forman la base de la programación orientada a objetos.
Encapsulación, Herencia y Polimorfismo en C#
Aprende encapsulación, herencia y polimorfismo en C# con ejemplos claros para dominar los principios básicos de la POO.
Interfaces y Clases Abstractas en C#
Aprende interfaces y clases abstractas en C#, sus diferencias y cuándo usar cada una para diseñar código limpio y extensible.
Métodos y uso de parámetros en C#
Aprende a definir métodos y usar parámetros en C#, incluyendo parámetros por valor y referencia, parámetros opcionales y ejemplos.
Namespaces y ensamblados en C#
Aprende los conceptos de namespaces y ensamblados en C# para organizar el código y gestionar dependencias correctamente.
Principios SOLID en C#
Aplicación de los principios SOLID en C# con ejemplos: código más flexible, mantenible y comprobable.