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.

Los principios SOLID

Los principios SOLID

SOLID es un acrónimo para una serie de principios definidos por Robert C. Martin, un famoso autor y arquitecto de software autor de varios libros que es posible que os suenen, como Clean Code, Clean Architecture o The Clean Coder, todos altamente recomendados para cualquier programador o trabajador del software.

En ellos se presentan una serie de principios que definimos como SOLID y que buscar la escalabilidad, claridad y escalamiento de todo el software que desarrollemos.

Estos cinco principios son los siguientes, respetando su nomenclatura inglesa:

En cada uno de los enlaces expuestos podreis encontrar más con más detalle en qué consiten cada uno de estos principios, en mi opinion absolutamente necesarios de comprender para la realizacion de un código que se pueda definir como profesional.

El patrón Facade

El patrón Facade

Imagina un sistema lleno de clases necesarias para realizar una determinada acción. Pongamos por ejemplo que queremos encender un ordenador, y tuvieramos que hacerlo de forma manual, llamando al POST, encendiendo el sistema de ventilacion, cargando el OS en la RAM, etc. ¿Sería un engorro verdad? Pues el botón que pulsamos y que desencadena una serie de procesos por nosotros es el patrón Facade. Nosotros no tenemos ni la menor idea de qué pasa dentro de la computadora, pero funciona simplificando los pasos que necesitamos, y si tenemos los conocimientos necesitarios incluso podemos refinar su proceso, es decir, no se nos veta el acceso a los componentes de los que la fachada nos abstrae.

Como siempre veamos un diagrama UML de las clases con las que vamos a ejemplificar el patrón.

 patron facade uml

Sobra decir que el anterior diagrama se ha hecho complejo a propósito, evitando desacoplamientos, etc para comprender mejor las ventajas de la fachada que provee el patrón.

Este diagrama con dependencias entre sí y sin un orden de carga definido es dificil de manejar y entender. Si quisieramos emular el comportamiento correcto del arranque de este sistema en un main() tendríamos algo similar a esto:

Desde luego, nada facil de leer, y con muchisimas dependencias. El patrón Facade nos permitirá generar una clase que llamando al menos numero de métodos posibles, automatice todo este código, abstrayendo al usuario del funcionamiento del programa y en este caso, evitando que se equivoque en el orden de ejecución, como podría ser administrar la corriente después de ejecutar el POST.

Si nos fijamos en el diagrama UML anterior vemos que pueden existir clases como FirmwareMotherboard que con su método ejecutarPOST() se encargan de abstraer la verificación y encendido de componentes como la Ram o los ventiladores(¡ya tenemos un ejemplo pequeño del patrón!), pero aun así, se nos queda corto para abstraer el encendido total del ordenador, necesitamos una fachada para todo el conjunto de clases y no solo para el encendido de algunos componentes. Algo en codigo similar a esto:

Y representado en UML así:

patron facade uml

Hemos conseguido encender el ordenador pero el cliente no ha necesitado conocer los entresijos de su lógica, simplemente ha llamado al método encenderOrdenador() de la clase TurnOnOffComputerFacade. ¡Y ojo! ¡Como hemos dicho no es la unica fachada del ejemplo!

En esencia el patrón Facade sirve para encapsular llamadas a métodos de distintas clases desde una clase que las contiene a todas y abstrae al usuario de su manejo.

Sin embargo este patrón puede tener una contrapartida con el que hay que tener cuidado: el no seguimiento del Principio de Mínimo Conocimiento.

El patrón Adapter

El patrón Adapter

La mejor forma de comenzar a comprender el patrón Adapter es presentar un símil con objetos de la vida real. Imaginemos que tenemos una lujosa y moderna tarjeta gráfica con salidas hdmi pero solo disponemos de una antigua pantalla con entrada VGA y  un cable hdmi. Es evidente que no podemos pasar información de un emisor digital a un receptor analógico, por lo que necesitamos algo capaz de traducir esa información entre ambos.

patron adapter uml

En este caso el adaptador se encargaría de transformar la salida digital en analógica haciendo la información accesible para el monitor VGA. Pongamos ahora un ejemplo de programación. Tenemos un conjunto de clases que representan objetos con una funcionalidad muy similar, por ejemplo aviones. Los distintos tipos de aviones heredan de una clase abstracta llamada Avión, tal y como se muestra en el siguiente UML:

patron adapter uml

Posteriormente creamos una clase Aeropuerto. Esta clase aeropuerto está pensada para almacenar aviones, por lo que creamos una lista de este tipo de objetos en uno de sus atributos. Pero unas semanas más tarde, nos damos cuenta de que ese aeropuerto no almacena solamente aviones, sino que también puede almacenar helicópteros… e incluso otro tipo de objetos como drones, o dirigibles.

patron adapter uml

En este momento nos planteamos las siguientes dos soluciones:

  • Hacer que Helicóptero herede de Avión.
  • Crear una interfaz (Aterrizable) para todos los objetos, hacer que la implementen tanto aviones como helicópteros y así poder almacenarlos en una lista del aeropuerto de tipo List.

La primera solución es un realmente mala. La clase abstracta Avion puede implementar métodos en el futuro incompatibles con los helicópteros, como por ejemplo desplegarFlaps() que no poseen un equivalente para los helicópteros, por lo que descartamos esta opción enseguida. La segunda opción, aunque deseable, no es posible en todos los casos. Por ejemplo, se puede dar el caso de que la clase forme parte de una API y/o no podamos modificar su código fuente. En ese caso tenemos que crear un adaptador que haga pensar que nuestros helicópteros son aviones. ¿Cómo hacemos esto?

  1. Creando una nueva clase que extienda de avión y que contenga un helicóptero
  2. Implementando los métodos de la clase abstracta de forma que se comporten como lo haría el helicóptero que lo contiene.

Siguiendo con el ejemplo del diagrama UML anterior y los dos pasos mencionados, la clase adaptadora nos quedaría de la siguiente forma:

Ahora la clase principal puede contener helicópteros siempre que estén contenido en un adaptador e incluso puede tratarlos como si fueran aviones, pero con el comportamiento de un helicóptero, de forma que cuando se llame a un metodo como aterrizar, la lógica de los mismos dependa del tipo de avion concreto o del adaptador de un objeto distinto. El diagrama UML final quedaría de la siguiente forma:

patron adapter uml

El patrón adapter es muy útil para situaciones en las que necesitemos gestionar distintas clases con comportamientos parecidos pero no podamos realizar cambios en el código fuente para abstraer una interfaz común a todas ellas. Por lo tanto, como ya mencionamos, puede ser muy útil para manejar cierta clase de APIs en nuestros programas.