Para utilizar todas las funcionalidades que ofrece este sitio, es necesario tener JavaScript habilitado.

En la entrada anterior estuve escribiendo acerca de la Inyección de Dependencias/Dependency Injection, para esta edición voy a explorar el patrón de diseño conocido como Decorador (Decorator). Veamos algunos ejemplos (voy a usar PHP):

Ejemplo 1: Juego de Pelea

Imaginemos que estamos trabajando en un juego de pelea, en donde cada jugador tiene su propio personaje. Inicialmente cada golpe produce un daño de 10 puntos. Ahora digamos que a medida que el jugador vaya coleccionando distintas piedras, aumenta el poder de sus golpes.

Teniendo en cuenta lo anterior, la clase encargada de calcular el daño que produce un golpe, se vería más o menos así:

La pregunta ahora es cómo, a partir de las piedras obtenidas, aumentamos la capacidad de causar daño a un oponente? Estudiemos algunas opciones:

  • Modificar la clase inicial: Esta no es una buena idea, ya que constantemente tendremos que modificar o crear métodos cada vez que queramos crear piedras nuevas. Si nos vamos a regir por las prácticas de desarrollo tipo SOLID, estaríamos violando el principio de Abierto/Cerrado.
  • Extender la clase inicial: Mejoramos, en comparación a nuestra primera opción. De esta forma no modificamos la clase inicial. Tendríamos que implementar diferentes subclases por cada piedra que tengamos.

Pero Que me dirías si te cuento que hay una forma más flexible, que nos permite implementar distintos comportamientos de forma separada y que adicionalmente se puedan ir agregando a medida que sea necesario?

El Decorador (Decorator)

Este patrón de diseño nos permite modificar, retirar o agregar responsabilidades a un objeto dinámicamente. Cuando digo dinámicamente, me refiero a que las funcionalidades se modifican/agregan/retiran durante la ejecución del script o aplicación.

La gran ventaja es que nos permite extender objetos incluso en situaciones cuando la extensión vía herencia no es viable o no es necesaria. Adicionalmente nos ayuda a conservar el principio de Abierto/Cerrado, en donde se dicta que cada entidad debe estar abierta a extensión pero cerrada a modificación.

Otra ventaja es que las decoraciones nos evitan la labor de crear clases complejas con mucho código, que en la mayoría de los casos no será evaluado. Nosotros podemos usar distintas combinaciones (o secuencias) de decoraciones para generar distintos comportamientos o resultados.

Resolvamos el problema inicial usando un decorador:

Fácil, no? Te presento el patrón Decorator en su forma más básica. Por otro lado podemos observar que hay algo de código repetido. Veamos como podemos mejorar la implementación:

En términos generales lo que acabamos de hacer es encapsular el objeto Damage dentro de una o varias decoraciónes. Con el método __call(), permitimos que se puedan llamar métodos de la clase base (Damage) diréctamente, conservando así compatibilidad con el API inicial. El uso de __call() no es estrictamente necesario pero dependiendo de la situación, es útil hacerlo.

Yo usé una clase abstracta para englobar los métodos comúnes para las decoraciones y así evitar la duplicación de código. Eso tampoco es necesario, pero yo prefiero hacerlo. 

También es posible usar interfaces y typehints para asegurar que cada objeto que será decorado, respete el API pactado (como una inyección de dependencias). En ese caso, al igual que con la clase abstracta hay que especificar los métodos comunes  de los objetos decoradores.

La ventaja de usar interfaces o una clase abstracta es que permite (hasta cierto punto) que las decoraciones sean intercambiables, recursivas y nos puede dar un poco de claridad cuando se produzcan errores.

Haz click aquí si quieres ver el ejemplo anterior usando una combinación de interfaces y una clase abstracta.

Ejemplo 2: Arepas

Imaginemos que tenemos una tienda virtual en donde vendemos arepas. Cada vez que agregamos un ingrediente a la arepa, el precio aumenta. Usemos los decoradores:

Ejemplo 3: Modificación de Texto

Asumamos que tenemos un texto y lo queremos modificar de distintas formas, lo queremos escribir solo en mayúsculas o dentro de varios tags de HTML.

Listo! En este último ejemplo me salté la creación de una clase abstracta que englobe los métodos comunes de las decoraciones. Lo hice para demostrar que ese paso no es realmente necesario, pero es algo que yo personalmente prefiero hacer. La duplicación de código nunca trae cosas buenas.

Desventajas del Patrón Decorador/Decorator

  • Si estas decorando demasiado a un objeto, vas a terminar con un montón de decoraciones (clases) pequeñas. Si tu código está muy desordenado, entonces vas a tener pesadillas tratando de encontrar las clases decorativas.
  • Aumento en la complejidad a la hora de instanciar el objeto a ser decorado. Al instanciarlo, debes envolverlo en quién sabe cuantas decoraciones, así que es importante usar el sentido común.
  • El exceso de decoraciones que agregan métodos a un objeto, puede ser problemático si no se especifican que decoraciones extienden el API del objeto y cuales son los nuevos métodos que se aportan. Esta situación también nos pueden causar dificultades cuando hay que debuguear una aplicación.
  • En algunos casos el programador debe aprender el orden en el cual las decoraciones deben ser instanciadas. Si vamos al último ejemplo, si el programador usa el decorador UpperCase al final, los tags dentro del HTML van a ser transformados a mayúsculas también. Por eso es vital que el programador entienda cuál es la intención detrás de cada decoración.

En Resumen

Como podrás ver, el Decorador/Decorator permite lograr cosas muy cheveres, extendiendo o modificando la funcionalidad de un objeto de forma dinámica, recursiva y transparente! De esta forma, así un objeto no nos pertenezca, nosotros lo podemos decorar para cambiar su comportamiento.

Los ejemplos funcionaron sin ningún problema en PHP 5.4 . Si tienes dificultades para ver el código puedes ir directamente a https://gist.github.com/mpratt/5572524. Recomiendo descargar los ejemplos y probarlos en tu entorno!

Si cometí algún error o tienes alguna crítica, me la puedes sacar en cara en los comentarios. Igualmente si quedaste con dudas o quieres darme una sugerencia sobre que patrón de diseño debería reseñar próximamente, dejame la inquietud.

Como siempre, Gracias por leer.

Comentarios