JavaScript — Patrones de diseño en JS — Estructurales (Parte II)
Temario
- Object — Bridge
- Object — Composite
- Object — Decorator
ii. Structural — Object — Bridge
El patrón de Bridge se usa cuando queremos dividir una clase grande en dos partes principales:
- Abstracción.
- Implementación.
El punto principal es que ambos deben funcionar de manera separada (no ser dependientes).
El patrón Adapter se usa cuando ya tenemos una aplicación y existe la necesidad de que clases incompatibles funcionen en conjunto, a diferencia el patrón Bridge se diseña desde el principio, lo que nos da ventaja de desarrollar la aplicación por partes y de manera independiente.
Veamos el siguiente ejemplo:
Podemos observar que es una clase bastante grande y cargada, entonces nuestro primer paso es dividir en clases y subclases correspondientes:
Ahora que se han dividido, utilicemos el patrón Bridge e identifiquemos quien abstrae y quien implementa:
La abstracción (interfaz) es la capa de control de alto nivel de alguna entidad, y que por sí sola no hace alguna tarea específica, por lo que delega el trabajo a la capa de implementación (plataforma).
De acuerdo a la definición de arriba, la impresora es la interfaz y el tipo de tinta es nuestra plataforma.
Ahora, veamos como se ve en código:
El primer paso es crear la clase Printer
y la clase Ink
En nuestro código de arriba podemos observar que la clase Printer
en su constructor espera una instancia de objeto de alguna subclase de Ink
. La clase Ink
solo espera el tipo de tinta que se va a usar, así como un método accesor del mismo.
Ahora vamos a generar las subclases de Ink
Lo que hicimos fue crear diferentes tipos de tinta.
Ahora generamos las subclases de Printer
:
En el código anterior, esperamos en el constructor la instancia de objeto de una subclase de Ink
, así como un método para imprimir los valores.
Veamos cómo se instancia:
— Ejemplo completo:
Podemos generar “los puentes” necesarios; en el siguiente ejemplo vamos a utilizar la clase Color
y la subclases BlackAndWhite, Colors
.
Lo primero que hemos hecho es ajustar la clase Printer
para que acepte la instancia de objeto de una subclase de Color
.
Ahora generamos la clase Color
:
Generamos las subclases de Color
:
Actualizamos las subclases de Printer
:
Por último hacemos los cambios necesarios en la instancia:
— Ejemplo completo
Inclusive todavía podemos ir más allá y usar el patrón Abstract Factory. Por ejemplo cuando ciertas abstracciones sólo pueden funcionar con ciertas implementaciones, con el patrón de Abstract Factory encapsulamos las relaciones y así ocultar la complejidad al cliente.
Se recomienda usar cuando…
- Necesitamos dividir una clase grande en varias clases y que sean útiles de manera independiente.
La ventaja de usarlo…
- Cumple con el principio de SOLID (Principio de responsabilidad única).
- Cumple con el principio de SOLID (Abierto / cerrado), podemos introducir abstracciones e implementaciones independientes.
- Podemos crear clases y aplicaciones independientes a nuestra aplicación.
- El código del cliente funciona con abstracciones de alto nivel (no se exponen los detalles de la aplicación).
La desventaja de usarlo…
- Cuando una clase es altamente coherente es más complicado aplicar el patrón.
Se puede combinar el patrón Builder con el patrón Bridge; donde la clase maestra desempeña el papel de la abstracción, mientras que los diferentes constructores actúan como implementaciones.
iii. Structural — Object — Composite
El patrón Composite nos permite componer objetos en estructuras de árbol y a su vez estas estructuras tratarlas como si fueran objetos individuales, y si alguna de ellas a su vez es una estructura de árbol, tratarla como si fuera un objeto individual.
Veamos un ejemplo, imaginemos que tenemos una caja (composición de objetos) y dentro tenemos “n” productos (objetos individuales):
Antes de empezar a trabajar en el patrón, primero identifiquemos las clases y subclases (para nuestro ejemplo vamos a simplificar un poco los productos), entonces, tenemos:
Para poder usar el patrón Composite, necesitamos crear una clase abstracta o una que tenga los métodos y propiedades comunes para administrar los objetos individuales.
Traduciendo lo que dice el párrafo anterior ( y aplicándolo a nuestro ejemplo), debemos tener una clase Composite que tenga:
- Una propiedad que vaya guardando los objetos individuales.
- Un método que permita “guardar” los objetos individuales.
- Método que recorra cada uno de los objetos individuales y haga su lógica.
Identificamos que nuestra subclase Box
es el objeto compuesto, entonces es a ella a quien le debemos de extender la clase Composite
; con ese cambio, el ejemplo quedaría de la siguiente manera:
Creamos la clase Article
:
Creamos la clase Composite
, donde extendemos de Article:
Creamos la clase compuesta, en este caso Box
:
Ahora solo nos queda crear las subclases que extiendan de la clase Article
:
La forma de instanciarlo es la siguiente:
— Ejemplo completo
Se recomienda usar cuando…
- Necesitamos una estructura de objeto compuesto en forma de árbol.
- El cliente deba usar los objetos simples y compuestos de igual manera.
La ventaja de usarlo…
- Cumple con el principio de SOLID (Abierto / cerrado), podemos introducir nuevos tipos de elementos en el programa sin romper el código del cliente existente.
- Trabajar con estructuras tipo árbol complejas de manera más sencilla.
- Usa recursividad y polimorfismo de manera sencilla.
La desventaja de usarlo…
- Puede ser complicado comprender la interfaz del componente ya que necesitamos hacerla lo más genérica posible, para que pueda cumplir con el patrón y no rompa la aplicación.
iv. Structural — Object — Decorator
El patrón decorator se encarga de agregar comportamiento o funcionalidad a las clases existentes de manera dinámica, en JavaScript es bastante sencillo, ya que este nos permite agregar métodos y propiedades a las subclases de manera flexible.
Veamos un ejemplo:
Imaginemos que nosotros vendemos hamburguesas, y que tenemos 3 tipos CheeseBurger, BaconDoubleBurger, WesternBurger
, donde cada una tiene un precio diferente; entonces nuestras clase y subclases tendrían más o menos la siguiente forma:
Ahora imaginemos que tenemos un cliente que quiere ponerle huevo, tocino, otra pieza de carne, o de queso…
Quizás estés pensando solucionarlo con el patrón de Composite, pero la verdad es de que no aplica, ya que su estructura jerárquica no es de tipo árbol, ya que realmente no queremos agregar una hamburguesa dentro de otra, sólo queremos agregar productos extra a nuestra hamburguesa.
Tal vez estés pensando en modificar la clase principal Hamburger
y agregarle las propiedades y métodos necesarios, algo así:
No es mala idea, pero… :
- Por cada ingrediente, necesitamos agregar una propiedad y sus métodos.
- Modificar el método de get price, para agregar las nuevas propiedades.
- ¿Qué pasa si un cliente quiere 3 carnes extras?.
- Rompemos con el principio de SOLID (Abierto-Cerrado).
Veamos entonces cómo sería con el patrón Decorator, primero vamos a realizar literalmente lo que dice el patrón, crearemos decoradores:
Para que una subclase cumpla como decorador, esta debe tener el mismo supertipo que el objeto al que van a decorar, esto quiere decir que extienden de la clase principal (en caso de nuestro ejemplo Hamburger
); ahora para cumplir con los principios de SOLID vamos a generar un super decorador, quien es el que va a extender de la clase principal, algo así:
Si vemos todo el ejemplo, quedaría más o menos de la siguiente manera:
Veamos paso a paso cómo queda con código…
Primero vamos a generar la clase Hamburger
:
Ahora vamos a crear nuestras las predeterminadas CheeseBurger, BaconDoubleBurger, WesternBurger
Podemos observar que en el constructor estamos asignando un nombre description
y un precio price
.
El siguiente paso es generar el super decorador HamburgerDecorator
:
Extiende de la clase principal Hamburger
, y guardamos la instancia de objeto de la subclase que mande.
Por último generamos cada uno de los decoradores:
Veamos cómo se instancia:
— Ejemplo completo
Podemos inclusive ir más allá y generar una closure[ref] y generar un mixin [ref] para hacer más amigable el pedido; solo necesitamos agregar esta función:
La forma de usar es la siguiente:
…más amigable ¿no?
Importante: Observar que en ningún momento estamos haciendo la instancia de objeto de la clase, solo la pasamos como argumento e internamente hacemos la instancia de objeto del modo composed (combinación de funciones).
— Ejemplo completo
Se recomienda usar cuando…
- Se necesite agregar comportamientos adicionales a los objetos en tiempo de ejecución sin romper el código.
- No sea posible extender el comportamiento de un objeto usando la herencia.
La ventaja de usarlo…
- Podemos extender el comportamiento de un objeto sin crear una nueva subclase.
- Podemos agregar/quitar comportamientos en tiempo de ejecución.
- Puede usar múltiples decoradores para envolver un objeto.
- Cumple con el principio de SOLID (Principio de responsabilidad única), ya que una clase que implementa muchas variables, podemos dividirla en subclases más pequeñas.
- Ya que los decoradores tienen el mismo tipo de objeto, podemos pasar objetos decorados en lugar de originales.
La desventaja de usarlo…
- Es complicado eliminar algún comportamiento en la pila de los contenedores.
- El código de configuración en capas puede verse muy feo.
- Los decoradores pueden complicar el proceso de creación de instancia de objeto, ya que no solo tienen que crear una instancia del componente, si no que también envolverlo en varios decoradores.
- El abuso o mal uso de este, puede romper la verdadera razón del patrón.
El patrón Adapter cambia la interfaz de un objeto existente, mientras que el patrón Decorator mejora un objeto sin cambiar su interfaz.
El patrón Decorator acepta la composición recursiva como el patrón Composite, con la diferencia que Decorator agrega comportamientos adicionales al objeto envuelto, mientras que Composite simplemente resume los resultados de sus hijos.
En la siguiente entrega vamos a ver JavaScript — Patrones de diseño en JS — Estructural (Parte III)
La entrega pasada vimos JavaScript — Patrones de diseño en JS — Creacional (Parte I)
Bibliografía y links que te puede interesar…
Iconos e imágenes…