OCP – Principio Open/Close

Continuando con el desarrollo de los principios de diseño SOLID, hoy vamos a ver en profundidad el principio OCP (principio abierto/cerrado).

Básicamente lo que nos dice este principio es que “las entidades de software (clases, módulos, funciones…) deben estar abiertas para extenderse, pero cerradas para modificación”.

Qué significa esto?

Que una entidad tiene que ser extendida sin necesidad de ser modificada internamente. Por ejemplo, deberíamos poder ampliar la funcionalidad de una clase sin necesidad de tener que modificar su comportamiento interno.

Parece simple, no?… sin embargo es uno de los principios que menos se respetamos a la hora de sentarnos a programar. Para entender este principio de una forma más clara, veamos un ejemplo que le puede resultar familiar. La idea es filtrar una lista de clientes por una serie de atributos: por localidad, nombre y deudores.

Primero  crearemos las entidades que vamos a utilizar en el ejemplo: vamos a representar las localidades en un enumerado, para simplificar la cosa, y vamos a crear una clase para representar a los clientes:

public enum Localidad
{
    Rafaela = 1,
    SantaFe = 2,
    Esperanza = 3,
    Sunchales = 4
}

public class Cliente
{
    public string Nombre { get; set; }
    public Localidad Localidad { get; set; }
    public decimal Saldo { get; set; }
}

Supongamos que en primer lugar, nos piden que necesitan filtrar la lista de clientes por la localidad a la que pertenecen. Para esto vamos a necesitar una clase que se encargue de hacer el filtro de los clientes (recuerden el principio SRP), a la cual llamaremos FiltroClientes.

El aspecto de la clase podría ser algo así:

public class FiltroClientes
{
    public IEnumerable FiltroPorLocalidad(IList clientes, Localidad localidad)
    {
        return clientes.Where(c => c.Localidad == localidad);
    }
}

Como podemos ver, el “filtrador” por localidades recibe como parámetros la lista de clientes, y la localidad por la cual filtrarlos y devuelve la lista de clientes que pertenecen a dicha localidad. Hasta acá todo perfecto, pero habitualmente lo que pasa es que el usuario comienza a pedir nuevos requerimientos. Imaginemos que ahora también quiere filtrar por el nombre y los que tienen deudas… qué es lo que habitualmente haríamos? Crear un método nuevo por cada filtro solicitado:

public class FiltroClientes
{
    public IEnumerable FiltroPorLocalidad(IList clientes, Localidad localidad)
    {
        return clientes.Where(c => c.Localidad == localidad);
    }

    public IEnumerable FiltroPorNombre(IList clientes, string nombre)
    {
        return clientes.Where(c => c.Nombre == nombre);
    }

    public IEnumerable FiltroPorSaldoNegativo(IList clientes)
    {
        return clientes.Where(c => c.Saldo < 0);
    }
}

Esto está perfecto, pero estamos violando el principio OCP. En primer lugar cada vez que tengamos que agregar nuevos filtros debemos modificar la clase FiltroClientes y por lo tanto NO está cerrado para modificación. En segundo lugar, la única forma de extender la funcionalidad de FiltroClientes es abriendo el archivo y modificarlo, por lo tanto NO está abierto a la extensión.

Entonces, como podemos hacer para respetar este principio?

Una solución es crear las especificaciones de los filtros en clases separadas, en donde internamente cada una va a implementar la forma de filtrar la información (nuevamente recuerden el principio SRP).  A su vez también para representar estas especificaciones podemos crear un template o patrón que tenga definida la estructura y el comportamiento que deben respetar dichas especificaciones. Nuestro template podría tener la siguiente forma:

public abstract class EspecificacionFiltroCliente
{
    public IEnumerable Filtrar(IList clientes)
    {
        return AplicarFiltro(clientes);
    }

    protected abstract IEnumerable AplicarFiltro(IList clientes);
}

En este caso el template es una clase abstracta que tiene definido dos métodos: el primero público que es sencillamente la acción filtrar y que es la que utilizará nuestro filtro de clientes. La acción filtrar internamente llamará a un segundo método que es el que nos encargaremos de sobre-escribir y ponerle la lógica de comportamiento correspondiente a cada especificación.

Ahora ya estamos listos para crear nuestras propias especificaciones de filtros de clientes. Por ejemplo para filtrar las localidades vamos a crear la clase FiltroLocalidad:

public class FiltroLocalidad : EspecificacionFiltroCliente
{
    private Localidad Localidad;

    public FiltroLocalidad(Localidad localidad)
    {
        this.Localidad = localidad;
    }

    protected override IEnumerable AplicarFiltro(IList clientes)
    {
       return clientes.Where(c => c.Localidad == this.Localidad);
    }
}

También vamos a reescribimos FiltroClientes:

public class FiltroClientes
{
   public IEnumerable FiltrarPor(IList clientes, EspecificacionFiltroCliente filtro)
   {
      return filtro.Filtrar(clientes);
   }
}

Finalmente veamos en un ejemplosu implementación mediante un ejemplo:

var clientes = new Cliente[] { new Cliente() { Localidad = Localidad.Rafaela, Nombre = "Sebis" },
                               new Cliente() { Localidad = Localidad.Rafaela, Nombre = "Sil" },
                               new Cliente() { Localidad = Localidad.SantaFe, Nombre = "Nico" },
                               new Cliente() { Localidad = Localidad.Sunchales, Nombre = "Fer" }
                             };

var filtrador = new FiltroClientes();
var clientesFiltrados = filtrador.FiltrarPor(clientes, new FiltroLocalidad(Localidad.Rafaela));

Ahora sí estamos respetando OCP. Como verán podemos extender la funcionalidad de FiltroClientes sin tener que modificar internamente su estructura, entonces esta cerrada para modifcación. Y podemos extenderla cuantas veces querramos simplemente creando nuevas especificaciones de filtros, entonces esta abierta para extensión.

Creo que este es uno de los principios más poderos de SOLID, pero requiere de un buen análisis por nuestra parte antes de ponernos a diseñar y crear las entidades.

Próximamente continuaremos con los restantes principios.

Anuncios

2 comentarios en “OCP – Principio Open/Close

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