ISP – Principio de Segregación de Interfaces

En esta cuarto post sobre los principios SOLID vamos a hablar sobre el principio ISP (Interface Segregation Principle).

Lo que nos dice este principio es bastante simple, y es que “los clientes no deberían ser forzados a depender de interfaces que ellos no usan”.

Básicamente lo que nos quiere decir este principio es que las clases que implementen una interfaz o una clase abstracta no deberían estar obligados a utilizar partes que no van a utilizar.

Veamos un ejemplo que esté violando este principio (aclaro que el ejemplo no tiene demasiado sentido, pero es útil para comprender el tema). Supongamos que tenemos la clase abstracta Proceso:

public abstract class Proceso
{
    public abstract void Iniciar();
    public abstract void Suspender();
    public abstract void Reanudar();
    public abstract void Finalizar();
}

Siguiendo con el ejemplo anterior vamos a trabajar con procesos (refiriéndonos con proceso a un tipo de tarea a llevar a cabo). En este definimos cuatro métodos, el primero y el último para iniciar y terminar el proceso, y el segundo y tercero para suspenderlo y reiniciarlo (estos dos últimos suponiendo que quien lo lleva a cabo la tarea es una persona y que debe cortar con la misma para descansar).

Ahora bien, supongamos que tenemos dos tipos de procesos (siguiendo con el ejemplo del post anterior) : unos van a ser manuales (ejecutado por personas) y otros automatizados (ejecutados por máquinas el 100% del tiempo):

public class Manual : Proceso
{
    public override void Iniciar()
    {
        //...
    }
    public override void Suspender()
    {
        //...
    }
    public override void Reiniciar()
    {
        //...
    }
    public override void Finalizar()
    {
        //...
    }
}

public class Automaizado : Proceso
{
    public override void Iniciar()
    {
       //...
    }
    public override void Suspender()
    {
        throw new NotImplementedException();
    }
    public override void Reiniciar()
    {
        throw new NotImplementedException();
    }
    public override void Finalizar()
    {
       //...
    }
}

En el proceso manual todos los métodos son implementados pero en el proceso automatizado los métodos suspender y reiniciar no son implementados, ya que las máquinas no necesitan tomarse un descanso y por lo tanto es necesario suspenderlo.

Como podemos ver en este caso estamos violando el principio ISP, ya que estamos obligando a una clase a implementar métodos que no va a utilizar.

Una solución a este problema es dividir un poco las cosas y crear, por ejemplo, una interfaz que tenga definida las cuestiones propias de los procesos manuales:

public interface IManuable
{
    public abstract void Suspender();
    public abstract void Reiniciar();
}

De esta forma la clase Proceso ahora tiene los métodos que son generales a todos los subtipos de procesos y evitamos a los procesos automatizados a implementar métodos que no les son útiles:

public abstract class Proceso
{
   public abstract void Iniciar();
   public abstract void Finalizar();
}

public class Manual : Proceso, IManuable
{
   public override void Iniciar()
   {
       //...
   }
   public override void Suspender()
   {
       //...
   }
   public override void Reiniciar()
   {
       //...
   }
   public override void Finalizar()
   {
       //...
    }
}

public class Automaizado : Proceso
{
   public override void Iniciar()
   {
       //...
   }
   public override void Finalizar()
   {
       //...
   }
}

En la próxima entrega vamos a estar hablando del último principio que nos queda.

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.

DictionaryExtensions : Excelente extensión de IDictionary

Gracias a José F. Romaniello les paso está excelente extensión de IDictionary, el cual nos permite recuperar un valor del diccionario a partir de una key y en el caso de que esta no exista en el diccionario, creará la entrada y luego retornará el valor:

public static class DictionaryExtensions
{
    public static TValue GetOrAdd(
        this IDictionary dictionary, TKey key, Func create)
    {
        TValue result;
        if (!dictionary.TryGetValue(key, out result))
        {
            result = create();
            dictionary[key] = result;
        }
        return result;
    }
}

Veamos un ejemplo muy simple de como usar esta extensión. Previamente creamos un diccionario con la clave del tipo int y el valor del tipo string y además le agregaremos cuatro entradas:

    IDictionary usuarios = new Dictionary();

    usuarios.Add(1, "Sebis");
    usuarios.Add(2, "Silvi");
    usuarios.Add(3, "Rafa");
    usuarios.Add(5, "Jose");

Finalmente crearemos un delegado encargado de devolver el valor que queremos agregar al diccionario en caso de que no exista una entrada con la key que especificaremos al llamar a nuestro método de extensión.

En el siguiente ejemplo veremos dos casos, el primero retorna el valor de una entrada que ya existe en el diccionario, y en el segundo retorna el valor de una nueva entrada que agregaremos en el diccionario:

    Func create = delegate() { return "Fernando"; };

    // En este caso como ya existe una entrada en el diccionario con la key 1 nos retornará su valor : "Sebis"
    string usuarioExistente = DictionaryExtensions.GetOrAdd(usuarios, 1, create);
    // En este caso como no existe una entrada en el diccionario con la key 4 nos creará y retornará el nuevo valor : "Fernando"
    string usuarioNuevo = DictionaryExtensions.GetOrAdd(usuarios, 4, create);

Espero que les sea de utilidad!