SOLID #2: Open/Closed Principle (OCP) con C#

En esta publicación veremos como implementar Open/Closed Principle usando el lenguaje C#.

Rodríguez Patiño, Eduardo
2020-09-28 | 8,449 lecturas
Actualizado:

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);