Introducción
El patrón de diseño Decorator es un patrón estructural que permite añadir funcionalidades adicionales a un objeto de manera dinámica. Este patrón es útil cuando se desea extender las capacidades de una clase sin modificar su código original, promoviendo así el principio de abierto/cerrado (Open/Closed Principle) de SOLID.
Conceptos Clave
- Composición sobre herencia: El patrón Decorator favorece la composición en lugar de la herencia para extender funcionalidades.
- Interfaz común: Tanto el objeto original como los decoradores implementan una interfaz común, lo que permite que los decoradores puedan ser intercambiables.
- Encapsulación de comportamiento: Los decoradores encapsulan el comportamiento adicional que se desea añadir al objeto original.
Estructura del Patrón Decorator
El patrón Decorator se compone de los siguientes elementos:
- Componente (Component): Define la interfaz común para los objetos que pueden tener responsabilidades adicionales.
- Componente Concreto (ConcreteComponent): Implementa la interfaz del Componente. Es el objeto al que se le pueden añadir responsabilidades adicionales.
- Decorador (Decorator): Mantiene una referencia al Componente y define una interfaz que cumple con la del Componente.
- Decorador Concreto (ConcreteDecorator): Extiende el Decorador y añade responsabilidades adicionales al Componente.
Diagrama UML
+------------------+ +------------------+ | Component |<------| Decorator | +------------------+ +------------------+ | +operation() | | +operation() | +------------------+ +------------------+ ^ ^ | | +------------------+ +------------------+ |ConcreteComponent | |ConcreteDecorator | +------------------+ +------------------+ | +operation() | | +operation() | +------------------+ +------------------+
Ejemplo Práctico
Supongamos que estamos desarrollando una aplicación de cafetería donde se pueden pedir diferentes tipos de bebidas y añadirles complementos como leche, azúcar, etc. Utilizaremos el patrón Decorator para añadir estos complementos de manera dinámica.
Paso 1: Definir la Interfaz Componente
Paso 2: Implementar el Componente Concreto
public class Espresso implements Beverage { @Override public String getDescription() { return "Espresso"; } @Override public double cost() { return 1.99; } }
Paso 3: Crear la Clase Decorador
public abstract class CondimentDecorator implements Beverage { protected Beverage beverage; public CondimentDecorator(Beverage beverage) { this.beverage = beverage; } @Override public String getDescription() { return beverage.getDescription(); } }
Paso 4: Implementar los Decoradores Concretos
public class Milk extends CondimentDecorator { public Milk(Beverage beverage) { super(beverage); } @Override public String getDescription() { return beverage.getDescription() + ", Milk"; } @Override public double cost() { return beverage.cost() + 0.50; } } public class Sugar extends CondimentDecorator { public Sugar(Beverage beverage) { super(beverage); } @Override public String getDescription() { return beverage.getDescription() + ", Sugar"; } @Override public double cost() { return beverage.cost() + 0.20; } }
Paso 5: Uso del Patrón Decorator
public class CoffeeShop { public static void main(String[] args) { Beverage beverage = new Espresso(); System.out.println(beverage.getDescription() + " $" + beverage.cost()); beverage = new Milk(beverage); System.out.println(beverage.getDescription() + " $" + beverage.cost()); beverage = new Sugar(beverage); System.out.println(beverage.getDescription() + " $" + beverage.cost()); } }
Salida Esperada
Ejercicio Práctico
Ejercicio 1
Implementa un nuevo decorador llamado WhippedCream
que añade crema batida a una bebida. El costo adicional de la crema batida es de $0.70.
Solución
public class WhippedCream extends CondimentDecorator { public WhippedCream(Beverage beverage) { super(beverage); } @Override public String getDescription() { return beverage.getDescription() + ", Whipped Cream"; } @Override public double cost() { return beverage.cost() + 0.70; } }
Ejercicio 2
Crea una nueva bebida llamada Latte
que tiene un costo base de $2.50. Luego, utiliza los decoradores Milk
, Sugar
y WhippedCream
para crear un Latte
con todos estos complementos y muestra la descripción y el costo total.
Solución
public class Latte implements Beverage { @Override public String getDescription() { return "Latte"; } @Override public double cost() { return 2.50; } } public class CoffeeShop { public static void main(String[] args) { Beverage beverage = new Latte(); System.out.println(beverage.getDescription() + " $" + beverage.cost()); beverage = new Milk(beverage); System.out.println(beverage.getDescription() + " $" + beverage.cost()); beverage = new Sugar(beverage); System.out.println(beverage.getDescription() + " $" + beverage.cost()); beverage = new WhippedCream(beverage); System.out.println(beverage.getDescription() + " $" + beverage.cost()); } }
Salida Esperada
Conclusión
El patrón Decorator es una poderosa herramienta para añadir funcionalidades a objetos de manera flexible y dinámica. Al seguir el principio de composición sobre herencia, este patrón permite extender las capacidades de los objetos sin modificar su código original, facilitando así el mantenimiento y la escalabilidad del software.
En el siguiente módulo, exploraremos otros patrones estructurales que también nos ayudarán a organizar y estructurar nuestro código de manera eficiente.
Curso de Patrones de Diseño de Software
Módulo 1: Introducción a los Patrones de Diseño
- ¿Qué son los Patrones de Diseño?
- Historia y Origen de los Patrones de Diseño
- Clasificación de los Patrones de Diseño
- Ventajas y Desventajas de Usar Patrones de Diseño
Módulo 2: Patrones Creacionales
- Introducción a los Patrones Creacionales
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
Módulo 3: Patrones Estructurales
Módulo 4: Patrones de Comportamiento
- Introducción a los Patrones de Comportamiento
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
Módulo 5: Aplicación de Patrones de Diseño
- Cómo Seleccionar el Patrón Adecuado
- Ejemplos Prácticos de Uso de Patrones
- Patrones de Diseño en Proyectos Reales
- Refactorización Usando Patrones de Diseño
Módulo 6: Patrones de Diseño Avanzados
- Patrones de Diseño en Arquitecturas Modernas
- Patrones de Diseño en Microservicios
- Patrones de Diseño en Sistemas Distribuidos
- Patrones de Diseño en Desarrollo Ágil