SLP – Principio de Sustitución de Liskov

Barbara H. Liskov

Barbara H. Liskov

En esta entrada vamos a ver el tercer principio SOLID llamado LSP (Principio de Sustitución de Liskov) escrito por Barbara Liskov nee Huberman, un gran referente de la comunidad informática.

Este principio lo que dice es que: “las clases derivadas deben poder ser sustituidas por sus clases bases”.

Qué significa esto?

Suponiendo que tenemos una clase base llamada ClaseBase y dos subclases llamadas ClaseSubBaseUna y ClaseSubBaseDos, en el código de nuestra aplicación siempre debemos hacer referencia a ClaseBase y no a las subclases definidas.

Un ejemplo en el cual podemos darnos cuenta fácilmente que estamos violando este principio es cuando tenemos clases con métodos que centralizan funcionalidad de distintos tipos de entidades. Por ejemplo: supongamos que tenemos una clase que se encarga de administrar información sobre la ejecución de los procesos en general.

Primero podríamos pensar en una clase base del tipo IProceso:

public abstract class IProceso
{
    public int IdProceso { get; set; }
    public string DescripcionProceso { get; set; }
    public DateTime FechaEjecucion { get; set; }
}

Y podríamos crear dos subclases, pensemos que tenemos procesos manuales (realizados por personas) y procesos automatizados (realizados por máquinas):

public class ProcesoManual : IProceso
{
    public Trabajador Trabajador { get; set; }
}

public class ProcesoAutomatizado : IProceso
{
    public Maquina Maquina { get; set; }
}

Finalmente creamos la clase AdministradorInformacionEjecucionProcesos que se encarguará de administrar la información relacionada con la ejecución de un proceso. Como primer paso podríamos setear la fecha de ejecución y registrar algunos datos comunes a todos los procesos:

public class AdministradorInformacionEjecucionProcesos
{
    private IRegistrador Registrador;

    public void AdministrarInformacionEjecucionProceso(IProceso proceso)
   {
        proceso.FechaEjecucion = DateTime.Now;
        Registrador.Registrar(proceso);
   }
}

Esto parecería que está muy bien: pocas líneas, simple y efectivo… pero supongamos que ahora se requiere que los procesos que son automatizados además de registrarse, deban notificarse vía e-mail. Una solución rápida podría ser la siguiente:

public void AdministrarInformacionEjecucionProceso(IProceso proceso)
{
    proceso.FechaEjecucion = DateTime.Now;

    ProcesoAutomatizado p = proceso as ProcesoAutomatizado;

    if (p != null)
    {
        NotificarPorEmailEjecucionProcesoAutomatico(proceso);
    }

     Registrador.Registrar(proceso);
}

Excelente, pero supongamos ahora que se requiere que también se notifiquen por email los procesos manuales ejecutados por los trabajadores de cierta área.

public void AdministrarInformacionEjecucionProceso(IProceso proceso)
{
    proceso.FechaEjecucion = DateTime.Now;

    ProcesoAutomatizado pa = proceso as ProcesoAutomatizado;

    if (pa != null)
    {
       NotificarPorEmailEjecucionProcesoAutomatico(proceso);
    }

    ProcesoManual pm = proceso as ProcesoManual;

    if (pm != null && pm.Trabajador.Area == Area.Logistica)
    {
       NotificarPorEmailEjecucionProcesoManual(proceso);
    }

    Registrador.Registrar(proceso);
}

Y si ahora nos solicitan que…. esperen, algo malo esta ocurriendo aquí! Lo que inicialmente registraba información común sobre la ejecución de los procesos, ahora se convirtió en otra cosa mucho más compleja… y de seguir teniendo nuevos requerimiento por cada proceso nuevo que aparezca, esto puede llegar a tener miles de líneas de código!

Qué esta pasando?

El problema surge debido a que estamos haciendo hincapié en la clases derivadas de IProceso. Esto hace que por cada solicitud sobre un proceso en particular, se tenga que modificar el comportamiento interno del método AdministrarInformacionEjecucionProceso (un punto importante a recordar es que también estamos violamos el principio OCP). Además esto puede provocar que me quede un método demasiado largo, con demasiadas responsabilidades en un mismo lugar(segundo punto importante, estamos violando el principio SRP), difícil de mantener y de testear.

La conceptos de registración y notificación si bien están relacionados en cierta manera, son diferentes para cada subclase de IProceso (al menos la notificación en este caso), por lo tanto es hora de separar un poco las cosas :).

Una forma de solucionar esto es viendo el mismo problema desde otra óptica. El primer paso será crear una clase que se encargue de tener la información que necesitamos sobre la ejecución de un proceso:

public abstract class InformacionEjecucionProceso
{
    public IProceso Proceso { get; set; }

    public void Informar()
    {
        InformarProceso();
    }

    protected abstract void InformarProceso();
}

Lo siguiente será crear los “informadores” de cada proceso:

public class InformacionEjecucionProcesoManual : InformacionEjecucionProceso
{
    protected override void InformarProceso()
    {
       NotificarPorEmailEjecucionProcesoManual(base.Proceso);
    }

    private void NotificarPorEmailEjecucionProcesoManual(IProceso iProceso)
    {
       ProcesoManual p = (ProcesoManual)iProceso;

       if (p != null && p.Trabajador.Area == Area.Logistica)
       {
           //...
        }
    }
}

public class InformacionEjecucionProcesoAutomatico : InformacionEjecucionProceso
{
    protected override void InformarProceso()
   {
       NotificarPorEmailEjecucionProcesoAutomatico(base.Proceso);
    }

    private void NotificarPorEmailEjecucionProcesoAutomatico(IProceso iProceso)
    {
        //....
    }
}

Finalmente refactorizamos nuestro administrador de información:

public class AdministradorInformacionEjecucionProcesos
{
    private IRegistrador Registrador;

    public void AdministrarInformacionEjecucionProceso(InformacionEjecucionProceso informacion)
    {
        informacion.Proceso.FechaEjecucion = DateTime.Now;
        Registrador.Registrar(informacion.Proceso);
        informacion.Informar();
    }
}

De esta forma las responsabilidades están dividas en cada clase, respentando el principio SRP, también respetamos el principio OCP ya que la clase está cerrada para modificación y abierta a extensión (por medio de nuevas clases InformacionEjecucionProceso para cada tipo de proceso que surja) y respetamos el principio de LSP pues no estamos haciendo uso de ninguna de las subclases.

Esta es solo una de las formas de solucionar el problema, pero existen muchísimas otras dependiendo del problema presentado.

Anuncios

Un comentario en “SLP – Principio de Sustitución de Liskov

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