El patrón de diseño Decorator se utiliza cuando se desea añadir funcionalidades adicionales a un objeto de forma dinámica, sin modificar su estructura. Esto permite extender las capacidades de un objeto de forma flexible y sin comprometer su estructura.
Un caso de uso común para este patrón es cuando se necesita añadir funcionalidades adicionales a un objeto en tiempo de ejecución. Por ejemplo, si se tiene una clase que representa una ventana en una aplicación, y se desea añadir la capacidad de desplazamiento a la ventana en tiempo de ejecución, se puede utilizar el patrón Decorator para crear una nueva clase que extienda la funcionalidad de la ventana base.
Otro caso de uso es cuando se tienen varias opciones de configuración para un objeto, y se desea crear varias versiones del mismo objeto con diferentes configuraciones. En lugar de crear una clase separada para cada combinación de configuraciones, se pueden utilizar los decoradores para añadir las configuraciones necesarias de forma dinámica.
Gráficamente, así podemos describir el patron de diseño Decorator.
Aquí tienes un ejemplo de diagrama en modo texto del patrón de diseño Decorator:
+-----------------------+
| Component |
+-----------------------+
/ \
|
|
+-----------------------+
| Decorator |
+-----------------------+
/ \
/ \
/ \
/ \
+-----------------+ +-----------------+
| Concrete | | Concrete |
| Component | | Decorator |
+-----------------+ +-----------------+
En este diagrama, la clase Component
representa la interfaz que define las operaciones que pueden ser realizadas por el objeto que se va a decorar. La clase Concrete Component
es la implementación concreta de la interfaz Component
.
La clase Decorator
es la clase abstracta que implementa la interfaz Component
y tiene una referencia a la interfaz Component
. La clase Concrete Decorator
es una implementación concreta de la clase Decorator
y agrega comportamiento adicional al objeto decorado.
Java
Aquí tenemos un ejemplo en Java.
Empecemos por crear la interfaz Shape
que define el método draw()
que será implementado por cualquier forma concreta que creemos:
public interface Shape {
void draw();
}
A continuación, crearemos una forma concreta llamada Rectangle
que implementará la interfaz Shape
:
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Rectangle");
}
}
Ahora crearemos el primer decorador, ShapeDecorator
, que también implementará la interfaz Shape
. Esta clase abstracta tendrá un objeto Shape
como propiedad y su método draw()
simplemente llamará al método draw()
del objeto Shape
.
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
public void draw(){
decoratedShape.draw();
}
}
A continuación, crearemos los decoradores concretos, en este caso, un decorador que agrega borde a una forma:
public class BorderDecorator extends ShapeDecorator {
public BorderDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setBorder(decoratedShape);
}
private void setBorder(Shape decoratedShape) {
System.out.println("Border Color: Red");
}
}
Y otro decorador concreto que agrega color a una forma:
public class ColorDecorator extends ShapeDecorator {
public ColorDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setColor(decoratedShape);
}
private void setColor(Shape decoratedShape) {
System.out.println("Fill Color: Blue");
}
}
Finalmente, podemos utilizar estos decoradores para agregar funcionalidades adicionales a la forma concreta que hemos creado, en este caso, un rectángulo:
public class Client {
public static void main(String[] args) {
Shape rectangle = new Rectangle();
// Agregamos un borde rojo al rectángulo
Shape redBorderRectangle = new BorderDecorator(rectangle);
// Agregamos un color azul al rectángulo con borde rojo
Shape blueFilledRedBorderRectangle = new ColorDecorator(redBorderRectangle);
System.out.println("Rectángulo normal:");
rectangle.draw();
System.out.println("\nRectángulo con borde rojo:");
redBorderRectangle.draw();
System.out.println("\nRectángulo con borde rojo y relleno azul:");
blueFilledRedBorderRectangle.draw();
}
}
En este ejemplo, el rectángulo normal se dibuja sin ningún decorador. Luego, creamos un rectángulo con borde rojo y, finalmente, creamos un rectángulo con borde rojo y relleno azul. Cada decorador agrega una nueva funcionalidad a la forma original sin modificar su estructura interna.
Python
Este es otro ejemplo, ahora en Python.
# Component Interface
class Pizza:
def get_cost(self):
pass
def get_description(self):
pass
# Concrete Component
class PlainPizza(Pizza):
def get_cost(self):
return 4.0
def get_description(self):
return "Thin dough"
# Decorator
class ToppingDecorator(Pizza):
def __init__(self, new_pizza):
self._temp_pizza = new_pizza
def get_cost(self):
return self._temp_pizza.get_cost()
def get_description(self):
return self._temp_pizza.get_description()
# Concrete Decorator
class Mozzarella(ToppingDecorator):
def __init__(self, new_pizza):
ToppingDecorator.__init__(self, new_pizza)
print("Adding Dough")
def get_cost(self):
return self._temp_pizza.get_cost() + 0.5
def get_description(self):
return self._temp_pizza.get_description() + ", Mozzarella"
class TomatoSauce(ToppingDecorator):
def __init__(self, new_pizza):
ToppingDecorator.__init__(self, new_pizza)
print("Adding Sauce")
def get_cost(self):
return self._temp_pizza.get_cost() + 0.35
def get_description(self):
return self._temp_pizza.get_description() + ", Tomato Sauce"
if __name__ == '__main__':
my_pizza = TomatoSauce(Mozzarella(PlainPizza()))
print(f"{my_pizza.get_description()} - Cost: {my_pizza.get_cost():.2f}")
Este ejemplo es similar al ejemplo en Java que creamos anteriormente. Tenemos una interfaz Pizza
y una clase concreta PlainPizza
. Luego tenemos una clase abstracta ToppingDecorator
que implementa la interfaz Pizza
y tiene una referencia a un objeto de Pizza
. Las clases concretas Mozzarella
y TomatoSauce
heredan de ToppingDecorator
y agregan su propio comportamiento decorativo.
Finalmente, en el bloque if __name__ == '__main__':
, creamos una nueva pizza con decoradores de salsa de tomate y mozzarella y obtenemos su descripción y costo total.
Comentarios