Patrones de diseño: Decorator

Marcos Sorribas
MarMass
Published in
6 min readSep 14, 2016

En el desarrollo de software los patrones de diseño son una asignatura siempre necesaria para mejorar nuestro código diario. En mi caso particular y siendo sincero es un punto que debería mejorar bastante, tanto en conocimiento de nuevos patrones como en la aplicación de los que ya conozco -punto más importante si cabe-. Para solucionar este problema me he propuesto hacer una serie de post donde explicar algún patrón y así refrescar o aprender nuevos conocimientos sobre la marcha. Así pues, adelante con el patrón de diseño Decorator.

El problema

Imaginaros que tenemos un sistema de software ya montado. El sistema cuenta con su jerarquía de clases y componentes donde cada uno tiene sus responsabilidades las cuales se acaban traduciendo en su funcionalidad.

Ahora, imaginaros que por la razón que sea se desea añadir una nueva funcionalidad a un objeto ya existente en el sistema. ¿Qué opciones tenemos?

Lo ideal sería añadir esta nueva funcionalidad modificando lo menos posible del código existente que ya estaba funcionando. De esta forma minimizaríamos las posibilidades de introducir nuevos bugs en código que antes funcionaba bien.

Para intentar hacer más clara la explicación vamos a basarla entera sobre un ejemplo que me he inventado -mejor dicho, basado en uno de las referencias 😅 -:

Se trata de una interfaz que representa un sandwich y dos objetos que la cumplen, los cuales representan dos tipos específicos de sandwiches. Cada uno de dichos objetos son capaces de calcular su coste en función de los ingredientes que llevan.

Bien, imaginaros que ahora este sistema quiere aumentar su carta de sandwiches o mejor dicho añadir ingredientes especiales a dichos sandwiches como por ejemplo huevo. ¿Debemos crear los objetos MixtoConHuevo o VegetalConHuevo? ¿Y si queremos dar la posibilidad de añadir mayonesa? ¿MixtoConHuevoYMayonesa?

¿Que opciones tenemos?

Mi primera idea al escuchar estos nuevos requisitos es manteniendo la jerarquía de clases actual -implementando la interfaz sandwich- crear todas las nuevas clases necesarias como he mencionado anteriormente. Así, se seguiría manteniendo la jerarquía de clases actual y cada nuevo tipo de sandwich seria capaz de calcular su coste de forma sencilla pero tendríamos una explosión de clases importante, ¿no creéis? -una clase nueva por combinación posible de ingredientes-

Si lo pensamos mejor, se puede poner en la interfaz los ingredientes nuevos que haya y, en cada una de las clases existentes, responder si tienen o no ingredientes especiales. Finalmente, en base a ello calcular de nuevo el precio -feels like a genius 😎-

Pero claro si hacemos esto, en todas y cada una de las clases que cumplimentan esta interfaz se deben implementar los métodos. ¿Y si hay demasiadas clases en vez de solo dos? Además, hay que modificar la función de costes de TODOS los sandwiches ya existentes en el sistema. Y si queremos añadir nuevos ingredientes hay que modificar la interfaz de nuevo y las clases. ¿Y si cambia el precio de un ingrediente en función del sandwich? 😲🔫

Es más, como expertos desarrolladores que somos si cumplimos estrictamente los principios SOLID -debería escribir sobre ellos tarde o temprano-, la O* nos indica que las clases ya existentes deberían poder “extenderse” -no entendáis extenderse como herencia- para añadir nueva funcionalidad sin tener que modificarlas tal cual están en el sistema.

*Open/Close principle: “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification

Mierda, parece que nos lo estamos saltando. Al intentar añadir la nueva funcionalidad estamos modificando el código existente -por ejemplo el de la interfaz, las funciones de costes que ya estaban escritas…-.

Entonces, sigo pensando: ¿que opciones tenemos?.

Decorar Objetos [La idea]

El punto principal del patron Decorator es poder añadir nueva funcionalidad de forma dinámica a estructuras de código ya existentes SIN tener que modificarlas. Por lo tanto, esto lo convierte en una solución REAL a nuestro problema -existen miles más, esta es solo una de ellas-.

Dicho patrón está principalmente basado en la composición de objetos, de esta forma un “decorator” de un objeto, lo encapsula y le añade la nueva funcionalidad que queramos agregarle sin modificarlo -lo decora, se podría decir-. De esta forma no estaríamos violando el open/close principle debido a que todo el código existente se mantiene tal cual estaba en el sistema.

Un decorador de un objeto se trata de otro objeto del mismo TIPO (responde ante los mismos mensajes) -esto es la clave de la cuestión- que el objeto al que vamos a decorar, el cual compone de forma privada al objeto al que decoramos. De esta manera, el decorador puede recibir tanto los mensajes correspondientes a las nuevas funcionalidades como los antiguos de forma que el objeto que decora los responda -algo lioso, verdad?. Voy a intentar pintarlo-. Si pintamos una jerarquía de clases base de este patrón podría ser algo así:

Jerarquía genérica de clases con el patrón Decorator aplicado

Como se puede observar en la imagen, los objetosConcretos implementan o extienden de Objeto -en nuestro ejemplo Objeto se trata de una interfaz (sandwich) pero bien podría ser una clase abstracta o un objeto base normal-. Por su parte los objetosConcretos son nuestras clases Mixto y Vegetal.

También observamos que al mismo nivel se encuentra un nuevo ObjetoDecorator. Se trata de una clase abstracta que incluirá los métodos que van a ser necesarios reescribir por las clases que la extiendan para añadir la funcionalidad deseada -en nuestro ejemplo, ObjetoDecoratorConcreto1 será Egg y ObjetoDecoratorConcreto2 será Mayo-

Aplicando el patrón decorator

Vamos a empezar dibujando el mismo gráfico de arriba pero siguiendo nuestro ejemplo:

Jerarquía de clases con el patrón Decorator

Fijaros bien en que el nuevo IngredientDecorator es del mismo tipo que Mixto y Vegetal porque cumplimenta su misma interfaz, PERO que ademas esta compuesto con una variable privada que es del tipo Sandwich. Esta nueva clase abstracta es la que nos va a dar la flexibilidad para añadir los nuevos ingredientes.

Los ingredientes, los cuales heredan de IngredientDecorator, tienen “dentro de si” un sandwich que es capaz de calcular su precio, su función de coste será tan sencilla como llamar al precio del sandwich y sumarle el precio del ingrediente. ¿Y sabéis lo mejor de todo? Se puede componer hasta el infinito. Vamos a pedir unos cuantos sandwiches:

public class Mayo extends IngredientDecorator {
Sandwich sandwich;
public Mayo (Sandwich sandwich) {
this.sandwich = sandwich;
}
public float cost() {
return 0.2 + this.sandwich.cost();
}
}
public class Egg extends IngredientDecorator {
Sandwich sandwich;
public Egg (Sandwich sandwich) {
this.sandwich = sandwich;
}
public float cost() {
return 0.5 + this.sandwich.cost();
}
}
public class Mixto implements Sandwich {
public float cost() {
return 2.0;
}
}
/* Notese que faltan clases implementadas. Solo he implementado las
necesarias para entender el ejemplo*/

Una vez creadas las clases, mirar que fácil es crear instancias de los sandwiches que queramos: -looks so nice 🤗-

public class restaurant {
public static void main (String args[]){

Sandwich mySandwichMixto = new Mixto();
Sandwich mySandwichMixtoWithEgg = new Egg(new Mixto());
Sandwich mySandwichMixtoWithEggAndMayo = new Mayo(new Egg(new Mixto()));
mySandwichMixto.cost(); //2.0€
mySandwichMixtoWithEgg.cost(); //2.5€
mySandwichMixtoWithEggAndMayo.cost(); //2.7€
}
}

Conclusiones

Como tanto el decorator como los ingredientes son del mismo tipo podemos usar la interfaz original para obtener el coste. Además gracias a la composición de las clases podemos crear tantos sandwiches con combinaciones de ingredientes que queramos y la forma de calcular su coste seguirá siendo igual de sencilla. Y lo mejor de todo: NADA del código existente se ha modificado con lo que no se van a introducir bugs en el código que ya funcionaba bien.

La verdad que decorator es un patrón de diseño muy potente a tener en cuenta cuando queramos añadir nueva funcionalidad a elementos ya existentes en nuestro sistema así pues, apuntado queda.

Un saludo,

M.

Referencias

--

--

Marcos Sorribas
MarMass
Editor for

Mitad estudiante de Ingeniería Informática y ADE en la UC3M. Mitad desarrollador iOS en minube. Eterno proyecto de millonario 💸💸