SOLID – Principio de Inversion de Dependencias

SOLID – Principio de Inversion de Dependencias

El principio de inversion de dependencias nos dice que los sistemas son mas escalables y mantenibles si las dependencias de un módulo dependen de abstracciones y no de elementos concretos. El motivo de esto es la diferencia de volatibilidad de ambos. Mientras que una abstraccion apenas requiere cambios y estos se propagan a las implementaciones concretas, las implementaciones concretas tienden a acambiar con más facilidad. Esto implica que todos los módulos que dependan de elementos concretos sean más propensos al cambio en cascada, dificultando el mantenimiento del código. Por eso, es necesario aplicar una serie de prácticas siempre que sea posible:

  • No remitirse a clases concretas que cambien con facilidad: Nadie te niega que uses un String o una clase concreta nativa de Java, pero evita usar referencias a tus propias clases en desarrollo para evitar implantar cambios a cada minuto en las clases que la implementan.
  • No realizar herencias desde clases concretas volátiles: Por el mismo motivo del caso anterior, es mejor no usar la herencia a no ser que estemos realmente necesitados de esta relación.

Las ventajas de usar este principio de diseño son varias y en distintas fases del desarrollo:

  • Durante la fase de desarrollo de la aplicacion, no depender de concreciones nos facilitará el no reescribir varias veces código de módulos que dependan de ellos.
  • Durante la fase de test, la interdependencia entre clases evita un buen desempeño a la hora de realizar pruebas unitarias.
  • Durante la fase de mantenimiento, es mucho más sencillo realizar los cambios convenientes en los distintos módulos e incluso comprender sus dependencias.
Inyeccion de dependencias

Es posible que hayas escuchado alguna vez la expresion «inyección de dependencias» o «inversion de control». Si crees que tiene algo que ver con este principio, has acertado de pleno. La inyección de dependencias es un patrón de diseño central para algunos frameworks como Spring, que nos ayuda a desacoplar la creacion de los objetos de su utilizacion. Spring por ejemplo nos provee de un contenedor que se encarga de buscar y manejar el ciclo de vida de un determinado tipo de clases denominadas beans. Gracias a este contenedor podemos beneficiarnos de todo lo que supone seguir el DIP (Dependency Inversion Principle) que comentamos más arriba.

En este enlace puedes leer más sobre el Spring Container y la inyeccion de Dependencias.

SOLID – Principio de Segregacion de Interfaz

SOLID – Principio de Segregacion de Interfaz

Posiblemente este sea el principio más fácil de definir de todos los SOLID.

En pocas palabras aconseja crear interfaces específcas para cada actor y no grandes interfaces con métodos comunes a varios actores.

¿Por qué es útil el principio de segregacion de interfaces?

Imagina que tienes un módulo A que permite muchas operaciones en una única inerfaz y creamos una serie de módulos que use algunos de los métodos provistos por A. ¿Que pasa si queremos añadir nuevas funcionalidades en A y añadirlas a su superinterfaz? Las clases que dependen de A y usen esa interfaz para comunicarse tendrán que proveer algún tipo de código para esos métodos, aunque no los vayan a utilizar nunca. Segregando esa gran interfaz en funcion del actor que vaya a utilizarla, evita que otros actores tengan que implementar métodos que nunca van a usar.

Como quizás habrás podido observar, al hablar de actores y el nivel de conocimiento de estos, puede que te haya recordado vagamente al de responsabilidad única… y es que los principios de SOLID no son estancos entre sí.

SOLID – Principio de Sustitucion de Liskov

SOLID – Principio de Sustitucion de Liskov

El principio de sustitucion de Liskov tiene dos formas de formularse: la enrevesada y la de andar por casa. Yo voy a anunciar la de andar por casa, pero aquí vamos a entender la formal, formulada por Barbara Liskov en 1988.

La definición más fácil de manejar es la siguiente: “los objetos a los que hace referencia un programa deberían ser sustituibles por sus subtipos sin alterar el buen funcionamiento del programa”.

Personalmente me considero un tiquismiquis de las definiciones formales y matemáticas, así que vamos a ver la que enunció Liskov. Estoy completamente seguro de que si existe alguna duda con la definicion informal quedará aclarada.

Si por cada objeto de tipo o1 de tipo S hay un objeto o2 de tipo T,  para todos los procedimientos P definidos en términos de T, si el comportamiento de P no varía cuando o1 es sustituido por o2, entonces S es un subtipo de T.

Lee atentamente la definición anterior. Si traducimos esto a  código, lo que nos dice es algo como “si un método usa como parámetro un objeto de tipo T y se puede cambiar por otro objeto de tipo S sin alterar el buen funcionamiento del método, entonces S es un subtipo de T”.

Donde mejor se puede ver esto es en un método que usa una interfaz como parámetro. Efectivamente podríamos cambiar la firma del método por un parámetro que sea de un tipo concreto que use la interfaz, aunque violaríamos otros principios.

En última instancia lo que nos quiere decir el principio de sustitucion de Liskov es lo mismo de siempre: programa sobre interfaces de forma que el cliente no sepa qué objeto concreto está usando. ¡Polimorfismo!

El problema Cuadrado-Rectangulo

Siguiendo con el ejemplo del libro Arquitectura Limpia de Robert C. Martin que se está siguiendo para ilustrar estos principios SOLID, veamos un caso en el que se vulnera el LSP.

Podríamos pensar que un rectángulo es la generalizacion de un cuadrado, pero hacer eso incumpliría el principio de Liskov, pues un cuadrado no tiene los mismos métodos que un rectángulo y no son sustituibles en el método de Main.

La solucion propuesta en el libro que mantenemos como guía es la creacion de una sentencia de control que nos permita saber si estamos ante un rectángulo o un cuadrado en tiempo de ejecución. A mi no me gusta la solucion. Sinceramente prefiero que ambas figuras sean concreciones de la familia de los paralelogramos y, mediante una factoría se construyan estas figuras pasando parámetros como el alto, ancho y el angulo menos que formen sus lados. A fin de cuentas, tendríamos el mismo problema con los rombos y romboides. De esta forma mantenemos la coherencia geométrica y cumplimos el principio de Liskov.

Ahora lee la definición informal del comienzo.

SOLID – Principio Abierto/Cerrado

SOLID – Principio Abierto/Cerrado

El Open-Close Principle es uno de los cinco principios SOLID y busca, como todos ellos, el óptimo mantenimiento del código.

Este principio tiene como objetivo mantener en la cabeza del desarrollador que los módulos y todo el código que se escriba esté abierto a extender su funcionalidad en el futuro y evitar su modificación.

Por ejemplo, si tenemos una aplicación que imprima las nóminas de sus empleados, a la hora del desarrollo, debemos pensar en cómo diseñar el módulo del que depende la presentacion si algun día queremos que además se puedan imprimir en muchos y variados formatos.

La finalidad de este principio es aislar lo más posible al sistema de un cambio. Volviendo a la contabilidad: si tenemos un módulo que imprime los datos de las nóminas (ImprimirNomina) a partir de la peticion de un actor, este módulo no debe conocer la presentacion concreta final de los datos. ¡Si después quisiéramos cambiar la lógica de una presentacion tendríamos que cambiar el codigo fuente de la clase que imprime!

Una de las máximas de este principio es: mantén las partes críticas de tu aplicacion lo más aislada de los cambios posible, evitando que dependan de otras partes más volátiles y manteniendo la comunicacion mediante interfaces.

Si ahora quisiéramos presentar los datos en formato PDF además de Excel y Web, tendríamos que acudir a la clase nómina y cambiar el código fuente añadiendo un método para ello. Cosas tan nimias como un cambio en el nombre del método de cualquiera de las clases que presente los datos implicaría una necesaria refactorización en la clase Nómina. Por eso, es necesario depender de abstracciones, para mantener al sistema lo más aislado de los cambios posible a través de «contratos» o interfaces que aseguren que los cambios en la extension de un módulo no tendrán impacto en otros.

Ahora nuestro código está abierto a la extension, sin necesidad de modificar el código anterior! Hemos cumplido con el principio open-close.

SOLID – Principio de Resposanbilidad Única

SOLID – Principio de Resposanbilidad Única

Es posible que hayas visto en otros lugares la definicion de este principio como: «Una clase sólo debe tener una responsabilidad».

A pesar de que el anterior es un principio de diseño válido no es lo que, en palabras del teorizador de SOLID, el SRP (Single Responsability Principle) pretende.

Para Robert C. Martin, el principio de responsabilidad único es que un módulo sea responsable de uno y solo un actor.

Por ejemplo si tenemos una clase que representa un jugador de fútbol, esta no puede ser manejada por actores como el director deportivo, el presidente del club, el entrenador y el masajista a traves de métodos creados para ello en la clase Jugador, pues puede derivar en conflictos de código cruzado.

Imaginemos que tenemos una clase Jugador con tres métodos

  • recibirMasaje() efectuado por el masajista para los jugadores que rindan más
  • recibirFeedbackPositivo() llamado por el entrenador para aquellos jugadores que rindan más
  • getRendimiento() devuelve un valor usado para calcular los masajes y el feedback del entrenador

Por tanto tenemos dos actores de los cuales depende Jugador. Si modificamos el algoritmo de getRendimiento(), que en funcion de su salida modifica el tipo de feedback o la intensidad de masajes, el cambio en el cómputo de este rendimiento puede no satisfacer al resultado que se espera en ambos métodos.

Si el rendimiento en un momento dado se calcula con respecto al numero de kilómetros recorridos, pero luego el entrenador quiere que se compute con respecto a la media de goles en cada partido, se puede dar el caso de que el masajista, ajeno al cambio es este cómputo comience a tratar sólo a los delanteros y no a los defensas, provocando el desastre en el vestuario.

El principio indica que debemos separar el código del que dependen varios actores, de esta forma, cada actor podrá realizar su propio calculo de rendimiento, priorizando lo que desee cada uno para su cómputo a la hora de realizar una acción.

Ahora cada clase tiene un actor como máximo del que depende y la funcionalidad de estas clases tiene una lógica separada de todas las demás funcionalidades que proveen otros actores. Las clases Masaje y Feedback sólo tienen un motivo para cambiar el código fuente: que sus actores así lo consideren, mientras que antes la clase Jugador tenían dos motivos para cambiar su código en funcion de cómo se quería calcular el rendimiento.