La mejor forma de explicar este concepto es hacerlo al mismo tiempo que explicamos el principio SOLID de Inversion de Dependencias.
Este principio nos dice que las clases deben depender de abstracciones y no de implementaciones concretas.
Es decir, si tenemos una clase que hace uso de otra, creando una instancia estamos dependiendo de una implementacion en especifico
En el ejemplo de arriba, al momento en que nuestro servicio de pedidos crea uno nuevo, hace uso de una clase EmailService para enviar la información del pedido al cliente, esto nos genera un acoplamiento o dependencia fuerte de la clase EmailService.
Lo que el principio D de Solid nos recomienda es depender de una Interfaz
Veamos el mismo ejemplo pero ahora dependiendo de una interface
public interface IEmailService
{
Task SendTrackingInfo(Order orderinfo);
}
public class SMTPService : IEmailService
{
public async Task SendTrackingInfo(Order orderinfo)
{
//Codigo para enviar la informacion de envio al cliente
}
}
Heredando o ‘Implementando’ la interfaz estamos obligados a tener un método con las características establecidas. Haciendo uso de esto nos vemos obligados a cumplir con lo que la interfaz esta definiendo, en este caso, tener un método llamado SendTrackingInfo
Ok, ya estoy dependiendo de una interfaz y no de una implementación en concreto. ¿Cómo hago para usar ese servicio de Email en mi clase de pedidos?
Si nos ponemos técnicos, la inyección de dependencias es un patron de diseño que nos permite poder inyectar dependencias a una clase y que la clase no tenga que crear los objetos para esas dependencias
Antes teníamos que instanciar el objeto del tipo EmailService para poder utilizarlo. Con inyección de dependencias podemos recibir el objeto especifico mediante el constructor de nuestra clase. Aun mas allá, podemos especificar que queremos recibir la interfaz en nuestro constructor y que el framework se encargue de darnos la implementación necesaria.
Como hacemos esto?
En .NET podemos especificar dentro de nuestra clase Program.cs una interface y la clase que la implementa.
Para el ejemplo de envio de informacion de pedidos quedaria de la siguiente forma
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IEmailService, SMTPService>();
var app = builder.Build();
En la linea builder.Services.AddScoped<IEmailService, SMTPService>(); estamos especificando que en los lugares en que dependamos de la interfaz IEmailService, se haga uso de la clase SMTPService. Nuestra clase OrderService quedaria de la siguiente manera.
public class OrdersService
{
private readonly IEmailService _emailService;
//El servicio se inyecta meddiante el constructor de la clase
public class OrderService(IEmailService emailService)
{
_emailservice = emailService;
}
public async Task CreateOrder(Order order)
{
//Codigo para crear el pedido
//Eliminamos esta linea var emailService = new EmailService();
//utilizamos el servicio inyectado
await _emailservice.SendTrackingInfo(order);
}
}
El contenedor de dependencias de .NET nos ayuda a resolver en tiempo de ejecución
Esto hace mucho mas sencillo el hacer un cambio de este tipo
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IEmailService, SendGridService>();
var app = builder.Build();
Teniendo una nueva implementacion para el envio de correos (en este caso mediante SendGrid) solo realizamos el cambio en la clase Program.cs gracias a que nuestra clase OrderService depende de una interfaz (abstraccion) y no de clases concretas.
OrderService no depende de SPTMService si no de la interfaz IEmailService.inyectarles las dependencias necesarias y no tener que crearlas -> A nuestra clase OrderService le inyectamos mediante el constructor la implementacion concreta de la interfaz IEmailService