c# Object oriented design – ¿Qué estrategias usas para aplicar el principio Open Closed?

Si luego de leer el post anterior te has preguntado ¿Cómo puedo aplicar el principio Open/Closed? te recomiendo que sigas leyendo.
OCP - Estrategias de implementación
OCP – Estrategias de implementación

Artículos relacionados:

Para poder aplicar el principio Open/Closed podemos hacer uso de los siguientes patrones: Strategy y Template Method.

1. Patrón Strategy

Es un patrón de comportamiento que tiene como principal característica el intercambio de una familia de algoritmos.

Cuando usarlo:

Se recomienda usar este patrón cuando cada implementación es independiente. Por independiente me refiero a que no comparten funcionalidades entre ellas.

Escenario:

Tenemos una aplicación que tiene como una de sus funcionalidades el generar reportes en los siguientes formatos: Pdf, Excel y Word. Pero se necesita que el diseño soporte agregar nuevos formatos para la siguiente versión del sistema.

Código actual
    public enum TipoReporte
    {
        Pdf,
        Excel,
        Word
    }

    public class ServicioGeneracionReporte
    {
        public ResultadoReporte Dibujar(TipoReporte tipo, Parametro parametro)
        {
            switch (tipo)
            {
                case TipoReporte.Excel:
                    //Codigo
                    break;
                case TipoReporte.Pdf:
                    //Codigo
                    break;
                case TipoReporte.Word:
                    //Codigo
                    break;
            }
            return null;
        }
    }
Solución propuesta:
public interface IServicioGeneracionReporte
{
    ResultadoReporte Dibujar(Parametro parametro);
}

public class ServicioGeneracionReportePdf : IServicioGeneracionReporte
{
    public ResultadoReporte Dibujar(Parametro parametro)
    {
        //Codigo
    }
}

public class ServicioGeneracionReporteExcel : IServicioGeneracionReporte
{
    public ResultadoReporte Dibujar(Parametro parametro)
    {
        //Codigo
    }
}

public class ServicioGeneracionReporteWord : IServicioGeneracionReporte
{
    public ResultadoReporte Dibujar(Parametro parametro)
    {
        //Codigo
    }
}

public class ServicioGeneracionReporteXps : IServicioGeneracionReporte
{
    public ResultadoReporte Dibujar(Parametro parametro)
    {
        //Codigo
    }
}

Al usar el patrón Strategy cada clase representa una estrategia, de esta forma extendemos el funcionamiento sin la necesidad de tocar el código de las otras implementaciones. Algo en contra de usar este patrón es que se puede llegar a crear clases que solo tendrán un uso. Para conseguir aplicar OCP con este patrón podemos aplicar la técnica de Composición sobre herencia (Composition over inheritance).

2. Patrón Template Method

También es un patrón de comportamiento, pero a diferencia del patrón Strategy este define el esqueleto de un algoritmo en una clase base y permite que las subclases definan uno o más pasos del algoritmo sin cambiar la estructura.

Cuando usarlo:

Se recomienda usar el patrón Template Method cuando existen pasos que se repiten en todas las subclases.

Escenario 1:

Tenemos una aplicación que tiene como una de sus funcionalidades el enviar notificaciones a los siguientes roles: administrador, supervisor y contador. Pero se necesita que el diseño soporte agregar nuevas notificaciones para la siguiente versión del sistema.

Código actual:
public class ServicioNotificacion
{
    private void Enviar(Notificacion notificacion, int tipo)
    {
        var smtp = CrearClienteSmtp();
        AsignarCredenciales(smtp);
        var formatoCorreo = ObtenerFormatoCorreo(notificacion);
        var mensajeFinal = PrepararMensaje(formatoCorreo);

        var mail = new MailMessage();
        //Codigo

        switch(tipo) 
        {
            case 1://Formato Correo Administrador
                mail.Body = //Codigo
            break;
            case 2://Formato Correo Supervisor
                mail.Body = //Codigo
            break;
            case 3://Formato Correo Contador
                mail.Body = //Codigo
            break;
        }
        smtp.Send(mail);
    }
}
Solución propuesta:
public abstract class ServicioNotificacion<T>
{
    protected abstract string ObtenerFormatoCorreo(T parametro);

    private void Enviar(T notificacion)
    {
        var smtp = CrearClienteSmtp();
        AsignarCredenciales(smtp);
        var formatoCorreo = ObtenerFormatoCorreo(notificacion);
        var mensajeFinal = PrepararMensaje(formatoCorreo);

        var mail = new MailMessage();
        //Codigo
        mail.Body = mensajeFinal;

        smtp.Send(mail);
    }
}

public class ServicioNotificacionAdministrador : ServicioNotificacion<NotificacionAdministrador>
{
    protected override string ObtenerFormatoCorreo(NotificacionAdministrador parametro)
    {
        //Codigo
    }
}

public class ServicioNotificacionSupervisor : ServicioNotificacion<NotificacionSupervisor>
{
    protected override string ObtenerFormatoCorreo(NotificacionSupervisor parametro)
    {
        //Codigo
    }
}

public class ServicioNotificacionContador : ServicioNotificacion<NotificacionContador>
{
    protected override string ObtenerFormatoCorreo(NotificacionContador parametro)
    {
        //Codigo
    }
}
Escenario 2:

Esta clase se encarga de calcular el descuento que se hace a toda persona dependiendo de la categoría a la que pertenece.

Código actual:
public enum TipoRentaCategoria
{
    Primera,
    Segunda,
    Tercera,
    Cuarta,
    Quinta
}
public class CalculoRenta
{
    public decimal CalcularDescuentoSueldo(TipoRentaCategoria categoria, string ruc)
    {
        decimal descuentoSueldo;

        var persona = BuscarPersona(ruc);

        if (categoria == TipoRentaCategoria.Primera)
        {
            //Codigo
        }
        else if (categoria == TipoRentaCategoria.Segunda)
        {
            //Codigo
        }
        else if (categoria == TipoRentaCategoria.Tercera)
        {
            //Codigo
        }
        else if (categoria == TipoRentaCategoria.Cuarta)
        {
            //Codigo
        }
        else if (categoria == TipoRentaCategoria.Quinta)
        {
            //Codigo
        }

        //Codigo
        decimal descuentoSueldo;
        //Codigo
        var igv = ObtenerIgv();
        //Codigo
        return descuentoSueldo;
    }
}
Solución propuesta:
public abstract class CalculoRentaBase
{
    public decimal CalcularDescuentoSueldo(string ruc)
    {
        var persona = BuscarPersona(ruc);
        //Codigo
        var impustoRenta = CalcularImpuestoRenta();
        //Codigo
        decimal descuentoSueldo;
        //Codigo
        var igv = ObtenerIgv();
        //Codigo
        return descuentoSueldo;
    }
       
    protected abstract decimal CalcularImpuestoRenta();

}

public class CalculoRentaPrimeraCategoria : CalculoRentaBase
{
    protected override decimal CalcularImpuestoRenta()
    {
        //Codigo
    }
}
public class CalculoRentaSegundaCategoria : CalculoRentaBase
{
    protected override decimal CalcularImpuestoRenta()
    {
        //Codigo
    }
}
public class CalculoRentaTerceraCategoria : CalculoRentaBase
{
    protected override decimal CalcularImpuestoRenta()
    {
        //Codigo
    }
}
public class CalculoRentaCuartaCategoria : CalculoRentaBase
{
    protected override decimal CalcularImpuestoRenta()
    {
        //Codigo
    }
}
public class CalculoRentaQuintaCategoria : CalculoRentaBase
{
    protected override decimal CalcularImpuestoRenta()
    {
        //Codigo
    }
}

Al usar el patrón Template Method las subclases pueden extender los métodos abstractos sin necesidad de tocar el código principal. Algo en contra de usar este patrón es que se puede llegar a crear subclases que solo tendrán un uso. Este patrón se basa estrictamente en el uso de herencia lo cual a veces no es la mejor opción, desde mi punto de vista es mejor optar por aplicar la técnica de Composición sobre herencia (Composition over inheritance).

Conclusión

Para poder implementar el principio Open/Closed podemos hacer uso de los patrones Strategy y Template Method. El patrón Strategy permite el intercambio entre una familia de algoritmos. En cambio el patrón Template Method define un algoritmo en una clase base para que sus subclases puedan darle el comportamiento deseado a uno o más pasos. Ambos patrones tienen como punto en contra que se puede llegar a crear subclases que solo tendrán un uso. El patrón Template Method hace uso estricto de la herencia, por otro lado el patrón Strategy permite aplicar la técnica de Composición sobre herencia.

Si te gusto este post entonces por favor ayúdame a difundirlo y logremos que el conocimiento se expanda, para lograr esto dale me gusta, compártelo en tus redes sociales a tus amigos o suscríbete a mi canal RSS, Gracias :).
Referencias:
Metal Tip:

Este artículo lo escribí escuchando la canción EarthBound de la banda Draconian de Suecia, les comparto el enlace.

Happy coding and Stay Heavy lml

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s