El patrón Composite

El patrón Composite

Existen ocasiones en las que sabemos que un objeto va a estar compuesto de otros muchos, que un contenedor va a albergar distintos componentes, y que todos esos componentes van a tener un comportamiento o funcionalidad similar. En definitiva, sabemos que nuestra implementación va a tener una estructura jerárquica.

Este patrón define una serie de conceptos fundamentales para su comprensión:

  • Component: implementa un comportamiento común entre las clases mediante una interfaz de manipulacion a sus nodos hijos. Es el nodo principal del que derivan los demás.
  • Leaf: representa los nodos “hoja” (no poseen hijos). Define comportamientos para objetos primitivos.
  • Composite: Son los componentes del patrón. Extienden el Componente principal y pueden ser padres de otros nodos hijos.
  • Cliente: manipula objetos de la jerarquía a través del nodo principal.

Los clientes usan la interfaz de Component para interactuar con objetos en la estructura Composite. Si el nodo con el que interactúa es un nodo primitivo o «leaf» lo hace directamente, pero en caso de interactuar con un Composite, lo hace a través de la interfaz que provee el Component.

El diagrama UML del patrón es de esta forma:

Nota importante: Este diagrama difiere un poco del diagrama «oficial» para evitar el error, a mi juicio, de la implementacion de métodos inútiles como por ejemplo en la propia Wikipedia. Bajo la implementación tradicional, se requiere que las clases «leaf» lancen errores de tipo UnsupportedOperationException. Para mí no tiene mucho sentido cuando puedes crear un nuevo nivel de abstracción que además separe responsabilidades entre la clase que define la operacion (Component) y la que define los métodos jerárquicos (Composite).

Si nos damos cuenta, aquí los nodos «Leaf» no implementan los métodos requeridos para realizar la jerarquía, y solo ejecutan operaciones. Los Composite son los encargados de hacer que los Components (tanto Leafs como Composite) ejecuten una determinada acción.

A continuación expongo un ejemplo de código de una clase Composite y otra Component bajo el anterior diagrama. Vamos a crear un ejemplo con el mapa político de Europa. En nuestro caso, la interfaz común será Territorio, que podrá contener otros territorios, paises y provincias consecutivamente hasta llegar a los nodos hijos finales: las ciudades. La finalidad será contar el número de habitantes.

Este patrón permite la generación de una jerarquía fácilmente, asi como una interfaz de acceso a toda la jerarquía para el cliente, aislándolo de la implementación concreta y por tanto siguiendo con los principios de diseño.

El patrón Iterator

El patrón Iterator

En Java como en cualquier lenguaje de programacion moderno orientado a objetos, existen multitud de tipos de listas y colecciones. Algunas de ellas permiten añadir punteros nulos, otras relacionan una clave a un valor y otras tienen una lógica interna que permiten comportamientos muy particulares y las hacen idóneas para según qué escenarios. Sin embargo todas ellas necesitarán eventualemente recorrer sus elementos, bien sea para ordenarlos o para ejecutar una acción sobre cada uno de ellos.

Evidentemente esto no es un problema en sí mismo, salvo que existan multitud de colecciones de distintos tipos que tenga que manejar un solo objeto. Si hacemos caso omiso a las estrechas dependencias que se crean entre las clases, no supone mayor problema, pero los patrones de diseño nos obligan a pensar en el largo plazo, y sabemos que una clase que depende de muchas implementaciones concretas (en este caso tipo de colecciones) puede ser un quebradero de cabeza. La solución que se nos puede pasar de primeras por la cabeza es la abstracción. En java por ejemplo, todas las colecciones salvo los mapas, heredan de la interfaz Collection, pero eso tampoco nos ayuda demasiado, ya que agrupaciones como los arrays o los mapas no heredan de Collection.

Se podría decir que java nos da una solución de serie, que es la de implementar en una clase la interfaz Iterator, de forma que no tengamos que saber qué tipo de coleccion contiene la clase, sino que es iterable y por tanto podemos navegar por los elementos que contiene.

Sin embargo, no puede ser tan fácil. Existe un principio de diseño que nos dice que hay que encapsular aquel código que depende de un único actor. Dicho de otra forma: debemos aislar el código que sólo tenga un motivo para cambiar. Fíjate que ahora la clase que contiene las colecciones tiene dos razones para cambiar, por ejemplo la forma de manejar los elementos y la forma de recorrerlos. Necesitaremos por tanto desarrollar una clase que cumpla este cometido concreto, implementando la interfaz Iterator.

Imaginemos por ejemplo una tienda de ropa con distintos proveedores. Cada proveedor tiene una forma distinta de organizar su ropa. Un proveedor la almacena en un Array y otro la almacena en un ArrayList. Ambas formas de organizar las prendas impican una forma distinta para iterar sobre ellas.

Como dueños de un comercio necesitamos acceder a la ropa de esos proveedores, pero tenemos el problema de depender de (por el momento) dos tipos de colecciones concretas. Es decir, si obtenermos las prendas mediante getters obtendremos en cada caso un array y una lista respectivamente. No queremos depender de una forma de iterar la ropa concreta para cada proveedor, por lo que creamos un objeto que se encargue de abstraer esta funcionalidad y que las tiendas (sus clientes) puedan usar. Este objeto deberá ser creado por los propios proveedores a traves de un método que devuelva un objeto iterable.

Si vemos detenidamente el diagrama UML anterior, podemos apreciar rápidamente ciertas formas de abstraer al cliente de las implementaciones concretas de los proveedores. Hemos dicho que todos los proveedores tenían que «entregar» un objeto de tipo Iterator al cliente, por lo que somos capaces de generalizar este comportamiento y crear una interfaz para todos los proveedores. De esta forma no dependemos de implementaciones concretas en el cliente, sino de una abstraccion: el Proveedor.
Ahora tenemos un cliente desacoplado tanto de los proveedores como del tipo de colecciones que usan.
El patrón Template Method

El patrón Template Method

La encapsulacion está a la orden del día en los patrones de diseño. Encapsulamos para simplificar y abstraer procesos, como la creacion de objetos o la llamada a multiples funciones de diversos objetos. El Template Method busca encapsular algoritmos, una secuencia finita de acontecimientos que llevan a un resultado.

El patrón Template Method busca simplificar, mediante la llamada a un método, la resolucion de un algoritmo con diferentes partes y cuya lógica depende de las clases concretas.

Por ejemplo, pongamos que queremos preparar pasta. Para hacer pasta todos los platos requieren cocer la pasta y añadirle sal y/o diversas hierbas. Si no abstraemos la lógica común a todos los platos tendremos código duplicado entre clases, algo que no queremos que ocurra.

En realidad, todos estos métodos pueden encapsularse en una sola llamada desde una clase abstracta que englobe las demás clases. Esta clase abstracta tendrá un método que podremos llamar preparar() y que mediante el uso de otro patron (el Facade) delegará su lógica en los métodos de las clases concretas. Por ejemplo, es posible que los tallarines haya que cocerlos menos tiempo que los macarrones, pero nosotros no lo sabremos, simplemente abstraeremos la lógica de la preparación en la clase Pasta y su método preparar().

Otros métodos como hervirAgua() y echarSal() no dependerán de la implementación concreta, por lo que podremos implementarlos en la propia clase abstracta. Los métodos que verdaderamente nos interesa obligar a implementar en las clases concretas son los que varían, y que también serán llamados por el método de la clase abstracta que ejecuta el algoritmo, preparar().

 

Lo que nos ha permitido el patrón Template Method es definir un algoritmo con una estructura predefinida pero con la implementación delegada en función del caso concreto.

¿Y qué pasa si queremos saltarnos un paso del algoritmo definido en el Template Method? Es posible que a alguien le guste la pasta sin añadir ningún condimento. En este caso tendríamos que evitar que el método preparar() de Pasta ejecutase el método condimentar() condicionalmente. Esto lo denominamos como un hook, un «gancho» en mitad de la ejecución a través del cual podemos modificar el comportamiento del método.

El patron Template Method nos rodea por todas partes. Está en todos lado. Allí donde necesitemos sobreescribir un método de una clase abstracta para dotar de funcionalidad a nuestra clase concreta, hay un Template Method. Los frameworks se nuetren de eso. Cuando queremos implementar una lógica específica entre muchas que podríamos implementar, por ejemplo, a la hora de comparar dos objetos, estamos usando un Template Method. Es posiblemente un patrón que estamos muy acostumbrados a usar gracias a la ligica de la herencia pero que contiene truquitos como los hooks que son convenientes tener en cuenta.

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.