
Patrones de Diseño: Decorador
- Dacadev
- Programacion
- February 21, 2024
Tabla de Contenido
Objetivo del patrón decorador
Note
El patrón decorador agrega dinamismo a la extensión de funcionalidades.
El objetivo del patrón decorador es crear una clase abstracta, la cual usaremos para crear (a través de herencia) los objetos base. Los decoradores serán clases especiales que extienden las funcionalidades de la clase abstracta, agregando un campo que contendrá una instancia de la clase abstracta, o en su defecto, un hijo de la misma.
Veamos un ejemplo de cómo los decoradores pueden funcionar. Supongamos que estamos creando una aplicación para llevar el control de costos de bebidas, donde el costo de estas bebidas depende de los ingredientes adicionales que le añadamos, como por ejemplo, chocolate rallado, crema, etc.
Ejemplo de uso
Ahora, supongamos que tenemos una clase base café a la cual queremos agregar dos ingredientes nuevos: chocolate y crema chantilly. Gráficamente, se vería algo como

De este ejemplo, podemos ver que para calcular el costo total de la bebida debemos sumar el valor de costo de cada uno de los decoradores o de las clases decoradoras. Para ello, la clase base debe tener la capacidad de identificar si tiene un hijo para retornar su valor de costo por defecto y así encadenar la suma.
Antes de pasar al código de implementación, veamos algunas características que debemos tener en cuenta para este patrón.
Características del patrón decorador
- El decorador debe tener el mismo supertipo que el objeto que están decorando; es decir, ambas clases, la clase decoradora y la que se está decorando, deben heredar de la misma clase.
- Puedes usar uno o más decoradores para encapsular un objeto, lo que significa que no hay límite en la cantidad de objetos decoradores que se utilicen sobre un objeto.
- Cuando se le da el mismo supertipo al decorado y a la clase que está decorando, podemos pasar dicho elemento decorado en lugar del objeto original encapsulado.
- El decorador agrega su propio comportamiento antes o después de que el objeto decorado realice su trabajo.
- Los objetos pueden ser decorados en cualquier momento, lo que permite que sean decorados de manera dinámica en tiempo de ejecución.
Note
🚀 El patrón decorador adiciona responsabilidades de manera dinámica a un objeto, ofreciendo flexibilidad y una alternativa al uso de subclases.
Implementación del ejemplo en código
Teoría
Para la implementación de nuestro ejemplo, veamos como funcionaría el diagrama de clases
Tendremos una clase base (nuestra clase abstracta) que en el diagrama se llama Component. De esta clase creamos dos clases: una es la clase ConcreteComponent
, que es la que usaremos para instanciar nuestros objetos, y otra clase llamada Decorator
, que es la que usaremos para crear todos los decoradores que podemos usar con nuestro objeto ConcreteComponent
.
Decorator
es una clase abstracta que será usada para crear nuestros decoradores específicos. Esta clase definirá los métodos y demás lógica que debe ser implementada para que otras clases sean decoradores válidos.
Ejemplo
Aplicando el anterior esquema a un ejemplo, podemos suponer que queremos crear tipos de bebidas que varían su combinación
La implementación del anterior diagrama se haría de la siguiente manera (en Python) empezaremos por la implementación de las clases abstractas
from abc import ABC
class Beverage(ABC):
def cost(self) -> float:
pass
def description(self) -> str:
pass
class IngredientDecorator(ABC):
def __init__(self, beverage: Beverage):
self.beverage = beverage
def cost(self) -> float:
pass
def description(self) -> str:
pass
Ahora haciendo uso de nuestras clases abstractas, podemos crear nuestros objetos y decoradores concretos, nuestros objetos lucirían de la siguiente forma:
from abc_classes import Beverage
class Coffee(Beverage):
def cost(self) -> float:
return 1.99
def description(self) -> str:
return "Coffee"
class Decaf(Beverage):
def cost(self) -> float:
return 1.25
def description(self) -> str:
return "Decaf"
class HouseBlend(Beverage):
def cost(self) -> float:
return 0.89
def description(self) -> str:
return "House Blend"
y nuestros decoradores
from abc_classes import IngredientDecorator
class Milk(IngredientDecorator):
def cost(self) -> float:
return self.beverage.cost() + 0.10
def description(self) -> str:
return self.beverage.description() + ", Milk"
class Mocha(IngredientDecorator):
def cost(self) -> float:
return self.beverage.cost() + 0.20
def description(self) -> str:
return self.beverage.description() + ", Mocha"
class Soy(IngredientDecorator):
def cost(self) -> float:
return self.beverage.cost() + 0.15
def description(self) -> str:
return self.beverage.description() + ", Soy"
class Chantilly(IngredientDecorator):
def cost(self) -> float:
return self.beverage.cost() + 0.25
def description(self) -> str:
return self.beverage.description() + ", Chantilly"
class Chocolate(IngredientDecorator):
def cost(self) -> float:
return self.beverage.cost() + 0.30
def description(self) -> str:
return self.beverage.description() + ", Chocolate"
y su uso dentro de nuestro código sería
from concrete_classes import Coffee, Decaf, HouseBlend
from decorators import Milk, Mocha, Soy, Chantilly, Chocolate
if __name__ == "__main__":
chocolate_blend = Chantilly(Chocolate(HouseBlend()))
soy_decaf = Soy(Decaf())
moccachino = Mocha(Chocolate(Milk(Coffee())))
print(chocolate_blend.cost())
print(chocolate_blend.description())
print(soy_decaf.cost())
print(soy_decaf.description())
print(moccachino.cost())
print(moccachino.description())
cuando ejecutemos este código podremos ver que en nuestro resultado obtendremos
1.44
House Blend, Chocolate, Chantilly
1.4
Decaf, Soy
2.59
Coffee, Milk, Chocolate, Mocha
Consideraciones del patrón decorador
- la herencia es una forma de extensión, pero no necesariamente la mejor forma de alcanzar la flexibilidad de nuestros diseños
- En nuestros diseños debemos de habilitar la extensión de nuevos comportamientos evitando la modificación del código actual
- la composición y la delegación pueden ser usados para agregar nuevos comportamientos en tiempo de ejecución
- El patrón decorador provee una alternativa al uso de subclases para extender el comportamiento
- El patrón decorador involucra el uso de clases decoradores que son usadas para encapsular componentes del mismo tipo en concreto
- Las clases decoradoras tienen el mismo tipo de la clase que decoran o encapsulan
- Los decoradores agregan nuevos comportamientos antes o después de que el objeto encapsulado realiza su trabajo
- Los decoradores normalmente son transparentes al cliente del componente
- Los decoradores pueden resultar en muchos objetos pequeños en nuestros diseños y el sobre uso del mismo puede derbar en crear código complejo y difícil de mantener