Un módulo o clase de un software solo debe estar abierto para su extensión pero cerrado para su modificación.
¿Qué quiere decir?
Una clase debe ser facilmente extendible sin tener que modificarse internamente.
Ejemplo
Supongamos que tenemos una clase para enviar notificaciones a nuestro cliente y nos han pedido que dicha clase debe soportar mandar notificaciones ya sea por SMS o e-mail, y en el futuro se podría considerarse otros canales como WhatsApp.
public class NotificationService
{
public async Task Send(List<Notification> notifications)
{
foreach (var notification in notifications)
{
if (notification.Type.Equals("sms"))
{
await SendbySMS(notification.PhoneNumber, notification.Subject);
}
if (notification.Type.Equals("email"))
{
await SendbyEmail(notification.Email, notification.Subject);
}
}
}
private async Task SendbySMS(string phoneNumber, string subject)
{
// Logica para mandar el SMS
}
private async Task SendbyEmail(string to, string subject)
{
// Logica para mandar el email
}
}
Esto rompe el principio
Si queremos agregar en un futuro cercano el envío por WhatsApp u otro canal tendremos que agregar un if adicional.
if (notification.Type.Equals("whatsapp"))
{
await SendbyWhatsApp(notification.PhoneNumber, notification.Subject);
}
if (notification.Type.Equals("slack"))
{
await SendbySlack(notification.Channel, notification.Subject);
}
if (notification.Type.Equals("telegram"))
{
await SendbyTelegram(notification.Channel, notification.Subject);
}
Miren como queda su uso
Horrible ..
var email = new Notification
{
Type = "email",
Email = "customer@email.com",
Subject = "El asunto del correo"
};
var sms = new Notification
{
Type = "sms",
PhoneNumber = "+051199999999",
Subject = "El mensaje del texto"
};
var notificationService = new NotificationService();
await notificationService.Send(new List<Notification> { email, sms });
Nota: ¿Se dieron cuenta que usamos la propiedades de la clase Notification para resolver distintos problemas?.
La solución
Separemos las lógicas del envío de notificaciones en clases distintas e implementos una interfaz. A través del contrato o interfaz, exigimos a que todas tengan un método en común.
public interface INotification
{
Task Notifiy();
}
public class NotificationEmailService : INotification
{
private readonly string _to;
private readonly string _subject;
public NotificationEmailService(string to, string subject)
{
_to = to;
_subject = subject;
}
public async Task Notifiy()
{
// Lógica para enviar la ntoification por e-mail
}
}
public class NotificationSmsService : INotification
{
private readonly string _phoneNumber;
private readonly string _subject;
public NotificationSmsService(string phoneNumber, string subject)
{
_phoneNumber = phoneNumber;
_subject = subject;
}
public async Task Notifiy()
{
// Lógica para enviar la notificación
}
}
NotificationService ahora cumple el principio
El siguiente código ya no necesitará ser modificado si se agrega nuevas formas de enviar notificaciones.
public class NotificationService
{
public async Task Send(List<INotification> notifications)
{
foreach (var notification in notifications)
{
await notification.Notifiy();
}
}
}
Veamos el uso
El código se reduce a esto y es fácilmente extendible.
var notificationsx = new List<INotification>
{
new NotificationEmailService("customer@email.com", "El motivo del correo"),
new NotificationSmsService("+05199999", "El asunto del mensaje de texto"),
// .. podemos implementar más notificaciones
};
var notificationService = new NotificationService();
await notificationService.Send(notificationsx);