DIP – Principio de Inversión de Dependencias

Un post que tenía pendiente hace mucho tiempo era acerca del quinto principio SOLID llamado Principio de Inversión de Dependencias (Dependency Inversion Principle). Este principio nos dice que “dependamos de abstracciones, no de concreciones”. Tío Bob plantea dos puntos en la definición de este principio:

A. Las clases de alto nivel no deberían depender de las clases de bajo nivel. Ambas deberían depender de las abstracciones. B. Las abstracciones no deberían depender de los detalles. Los detalles deberían depender de las abstracciones.

Este principio, en conjunto con el resto de los principio SOLID, están orientados a reducir problemas relacionados con el mal diseño (aún cuando tengamos el código bien organizado en clases). Tio Bob  dice que en todo mal diseñose presentan alguna de las siguientes características:

  • Rigidez: un cambio afecta a muchas partes de un sistema.
  • Fragilidad: cada cambio genere problemas en lugares inesperados.
  • Inmovilidad: imposible de reusar.

Una forma muy simple de detectar un mal diseño es cuando tenemos “miedo” de tocar el código, porque creemos que en alguna parte algo se va a romper. Veamos un ejemplo de código mal diseñado y como podemos mejorarlo aplicando los principios SOLID, sobre todo DIP. Continuando con el mismo tema de los últimos post de la serie, supongamos que tenemos la clase PrecesoAutorizacionTarea que contiene el método Iniciar(). Este método se encarga, entre otras cosas, de loggearinformación acerca del inicio del proceso y el plazo máximo de aprobación de la tarea:

Definición clase ProcesoAutorizacionTarea

Definición clase ProcesoAutorizacionTarea

Es fácil detectar varios “errores” de diseño en la implementación del método. El primero de ellos es que es un diseño rígido. Supongamos que tenemos que agregar una nueva propiedad a la clase TxtLog para especificar el path o file del archivo de log a utilizar? Esta claro que deberíamos cambiar todas las instancias de TxtLog en el código, salvo que quisiéramos registrar “todo” en el archivo de log especificado por default (pero en tal caso, no tendría sentido dicha propiedad).

Para resolverlo, supongamos que decido especificar esta propiedad “a mano” (logger.Path = “…”;) en todas las porciones de código que sean necesarias (es decir, donde no me sea útil el archivo por default). En tal caso nos encontrarímos con un diseño frágil, ya que si nos olvidamos de especificarlo, estaríamos registrando información en un archivo de log, que sería el incorrecto. Algo similar ocurre con el calculo del plazo máximo de autorización de la Tarea… qué pasa si necesito realizar el mismo cálculo en otro módulo? O qué pasa si ahora tengo un nuevo tipo de tarea? Es realmente responsabilidad de la clase ProcesoAutorizacionTarea realizar dicho cálculo?… esta claro que las respuestas a estas preguntan nos conducen ante un mal diseño. Bien, para poder solucionar estos problemas, vamos a realizar una serie de cambios, el primero respecto al lugar donde se instancia el objeto TxtLog:

Resolviendo problemas de rigidez y fragilidad

Resolviendo problemas de rigidez y fragilidad

Como podemos ver, ahora la instancia de TxtLog la recibe el constructor de la clase ProcesoAutorizacionTarea, por lo que cada cambio que tengamos que realizar sobre dicha instancia, podemos hacerlo en un único lugar. Notemos que en este caso estamos “inyectando” al objeto ProcesoAutorizacionTarea  un objeto TxtLog, es decir su dependencia. Esto se denomina Inyección de Dependencias y sobre este tema hemos hablado en varios post. Bien, resuelto esto, tenemos otro problema, seguimos teniendo un mal diseño, ya que es inmóvil. Supongamos que el día de mañana, se desea utilizar un DbLog o XmlLog. Este cambio podría ser un dolor de cabeza, ya que el proceso depende exclusivamente de TxtLog. Algo similar ocurre si necesitamos realizar el calculo de un proceso “SEMI-AUTOMATICO”? Entonces veamos como corregir este error de diseño. Antes que nada vamos a generar las interfaces ILog e ICalculadorPlazosTarea – esta última es una pobre traducción que se me ocurrió, reconozco que con el tiempo se me hizo complicado definir nombres en español 🙂 -.

Definiendo las interfaces

Definiendo las interfaces

Lo siguiente, generar las clases que lo implementen. Por un lado tendremos TxtLog que implementa ILog y por el otro dos nuevas clases llamadas CalculadorPlazosTareaX CalculadorPlazosTareaY que implementan ICalculadorPlazosTarea. Comencemos con TxtLog:

Redefiniendo TxtLog

Redefiniendo TxtLog

Ahora las nuevas clases “calculadoras de plazos”:

Implementaciones de ICalculadorPlazosTarea

Implementaciones de ICalculadorPlazosTarea

Bien, por último solo resta eliminar las dependencias (o mejor dicho invertirlas) de la clase ProcesoAutorizacionTarea:

Aplicando DIP a la clase ProcesoAutorizacionTarea

Aplicando DIP a la clase ProcesoAutorizacionTarea

Como podemos ver  el punto A del principio estaría resuelto con la intrdocción de ILog, ya que ahora tanto ProcesoAutorizacionTarea como TxtLog dependen de una abstracción (es decir, de ILog). También estaríamos cumpliendo del punto B, ya que por ejemplo, los métodos para calcular plazos dependen de la abstracción ICalculadorPlazosTarea y no de la clase ProcesoAutorizacionTarea. Espero que les sea de utilidad!

Anuncios

Patrones de diseño GoF

Los patrones de diseño son basicamente modelos que podemos utilizar para resolver exitosamente un problema. Otra definición valida es que son respuestas a problemas que se presentan una y otra vez en nuestras tareas cotidianas, soluciones probadas repetidamente.

En posts anteriores sobre OOD hablamos de los patrones SOLID y lo que ofrecían cada uno de ellos. En esta ocación vamos a ver los patrones de diseño GoF, sus siglas significan Gang of Four debido a sus creadores: Erich GammaRichard HelmRalph JohnsonJohn Vlisides.

Estos patrones de diseño tuvieron un gran éxito en el mundo de la informática a partir de la publicación del libro Design Patterns escrito a principios de los 90s y en el cual se recogen 23 patrones de diseño comunes.

Design Patterns

Design Patterns

Estos 23 patrones los vamos dividir en 3 grupos de acuerdo a la naturaleza de cada uno:

  • De creación: conciernen al proceso de creación de objetos.
  • Estructurales: tratan la composición de clases y objetos.
  • Comportamiento: caracterizan las formas en las que interactúan y reparten responsabilidades las distintas clases u objetos.

En posteriores post vamos a ir viendo uno por uno en detalle estos patrones, destacando las ventajas y desventajas de cada uno.

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.

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.

SRP – Principio de responsabilidad simple

Cómo comente en el post anterior, la idea es ir desarrollando cada uno de los principios S.O.L.I.D. un poco más en profundidad. El primero que vamos a ver, es el principio SRP (Principio de responsabilidad simple o Single Responsability Principle).

Básicamente los que nos dice este principio es que “una clase debe tener una, y solo una razón para cambiar“.

Qué significa esto?

Bajo el principio SRP, una razón es una responsabilidad, y una clase debería tener solamente una responsabilidad. En caso contrario, deberíamos delegar estas responsabilidades a nuevas clases.

Vamos a ver un ejemplo bien sencillo que nos va a ayudar a entender un poco mejor de qué se trata todo esto.

Supongamos que estamos trabajando en nuestra versión del juego PACMAN. Una de las primeras cosas que podríamos hacer, es crear una clase que represente los personajes del juego. La clase Personaje, tendría un atributo que representa la posición actual del personaje en el escenario y una acción que le permita avanzar de posición siempre que sea posible:

public abstract class Personaje
{
    public Posicion Posicion { get; set; }
    public void AvanzarCasillero(Posicion nuevaPosicion){}
}

Otra función que debería cumplir es la de comer otros personajes, por ejemplo Pacman puede comer fantasmas (siempre y cuando coma la píldora grande), en tanto que los fantasmas tienen como objetivo comerse a Pacman. Además de comer otros personajes, Pacman puede comer píldoras. Por lo tanto, todas estas acciones deberíamos incluirlas también:

public abstract class Personaje
{
   public Posicion Posicion { get; set; }
   public void AvanzarCasillero(Posicion nuevaPosicion){ }
   public void ComerPersonaje(Personaje personaje){ }
   public void ComerPildora(Pildora pildora) { }
 }

Cómo ya nos habremos dado cuenta, podemos ver que la clase Personaje tiene más de una razón para el cambio, ya que “comer píldoras” es responsabilidad únicamente de Pacman, y no del Fantasma.

Para aislar el método que está provocando “más de una razón para el cambio”, deberíamos crear las clases Pacman y Fantasma y delegar las responsabilidades que correspondan:

public abstract class Personaje
{
    public Posicion Posicion { get; set; }
    public void AvanzarCasillero(Posicion nuevaPosicion){ }
    public void ComerPersonaje(Personaje personaje){ }
}

public class Pacman : Personaje
{
    public void ComerPildora(Pildora pildora){ }
}

public class Fantasma : Personaje
{
}

De esta forma, tenemos bien definidas las responsabilidades de cada clase y ya no tenemos razones (al menos por ahora) para cambiarlas.

Beneficios de aplicar este principio:

  • Código más fácil de escribir y de entender.
  • Menos propenso a errores, ya que cada clase tiene una única responsabilidad bien definida
  • Facilita las tareas de testing : tenemos bien definido que debemos testear en cada clase.
  • Facilita las tareas de mantenimientos
  • Hace que nuestro código sea más robusto y extensible.

Como desventajas podríamos comentar que no siempre es muy simple asociar una única responsabilidad a una clase. También la “exageración” de aplicar este principio, podría llevarnos al extreme de llegar a tener una clase por método!… lo que nos llevaría a tener una gran cantidad de clases sin sentido y que solo nos confundirían.

Principios S.O.L.I.D.

Los principios S.O.L.I.D. son un conjunto de diseños y buenas prácticas que se emplean en OOD y OOP (diseño y programación orientada a objetos).

Estos principios fueron desarrollados y publicados por primera vez por Robert “Tío Bob” Martin hace más de una década. Martin dice que la mayoría de nosotros usamos lenguajes orientados a objetos sin saber por qué, y sin saber cómo obtener el beneficio máximo de ellos. Por este motivo es que surgen estos patrones de diseño.

Aplicar estos principios nos ayudaran, entre otras cosas, a crear un código que sea más legible, simple, reusable, mantenible, escalable… es decir, nos ayudaran a evitar los problemas con los que nos solemos encontrar a medida que nuestro código va creciendo.

Los principios que lo componen son:

  • SRP: The Single Responsibility Principle (Principio de Responsabilidad Única)
  • OCP: The Open/Closed Principle (Principio Abierto / Cerrado)
  • LSP: The Liskov Substitution Principle (Principio de Sustitución de Liskov)
  • ISP: Interface Segregation Principle (Principio de Segregación de Interfaces)
  • DIP: The Dependency Inversion Principle (Principio de Inversión de Dependencias)

Cómo podemos ver S.O.L.I.D. surge del acrónimo de estos principios.

En las próximas entradas iremos hablando de cada uno de estos principios, nombrando además las ventajas y desventajas de cada uno.

Más información en el enlace de Robert Martin: PrinciplesOfOod