Patrón Decorator
Parte de la sección Programación Orientada a Objetos del Journey de Java de Coddy. Lección 72 de 87.
El patrón Decorator es un patrón de diseño estructural que te permite añadir nuevos comportamientos a los objetos envolviéndolos en objetos especiales llamados decoradores. A diferencia de la herencia, que añade comportamiento en tiempo de compilación, los decoradores añaden funcionalidad dinámicamente en tiempo de ejecución sin modificar la clase original.
Imagina una cafetería donde empiezas con un café básico y puedes añadir extras como leche, azúcar o nata montada. Cada adición envuelve el café original y añade su coste y descripción. El patrón utiliza una interfaz común para que los objetos decorados puedan tratarse igual que el original:
interface Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}El decorador mantiene una referencia al objeto envuelto y delega las llamadas en él mientras añade su propio comportamiento. Puedes apilar varios decoradores para combinar funcionalidades:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Simple Coffee, Milk, Sugar
System.out.println(order.getCost()); // 2.75El patrón Decorator destaca cuando necesitas añadir responsabilidades a los objetos sin crear una explosión de subclases. Cada decorador es independiente, por lo que puedes combinarlos libremente para crear exactamente la combinación de comportamientos que necesitas.
Desafío
Fácil¡Construyamos un sistema de notificaciones usando el patrón Decorator! Crearás una notificación base que puede mejorarse con diferentes canales de entrega, alertas por correo electrónico, alertas por SMS y notificaciones push, envolviendo decoradores unos alrededor de otros. Esto refleja los sistemas del mundo real, donde un solo mensaje podría necesitar enviarse a través de varios canales simultáneamente.
Organizarás tu código en cuatro archivos:
Notifier.java: Define la interfazNotifierque sirve como interfaz del componente para tu patrón decorador. Debe declarar un métodosend(String message)que se encargue de enviar notificaciones.BasicNotifier.java: Crea la clase de componente concreta que implementaNotifier. Esto representa la forma más sencilla de notificación: una alerta dentro de la aplicación. Cuando se llama asend, debe imprimirIn-App: [message].NotifierDecorators.java: Construye aquí tu jerarquía de decoradores. Comienza con una clase abstractaNotifierDecoratorque implementeNotifiery mantenga una referencia a un objetoNotifierenvuelto (pasado a través de su constructor).Después, crea tres decoradores concretos que extiendan
NotifierDecorator:EmailDecorator: cuando se llama asend, primero delega en el notificador envuelto y después imprimeEmail: [message].SMSDecorator: cuando se llama asend, primero delega en el notificador envuelto y después imprimeSMS: [message].PushDecorator: cuando se llama asend, primero delega en el notificador envuelto y después imprimePush: [message].Main.java: ¡Reúne tu sistema de decoradores! Recibirás dos entradas: el mensaje de notificación (String) y una lista separada por comas de los canales que se añadirán (String), por ejemplo"email,sms"o"push,email,sms".Comienza con un
BasicNotifier. Después, según la entrada de canales, envuélvelo con los decoradores apropiados en el orden en que aparecen. Los nombres de canal válidos sonemail,smsypush.Finalmente, llama a
sendcon tu mensaje en el notificador completamente decorado.
Recibirás dos entradas en este orden: el mensaje que se enviará (String) y los canales que se habilitarán (String, separados por comas).
Por ejemplo, con las entradas Server is down! y email,push, el resultado sería:
In-App: Server is down!
Email: Server is down!
Push: Server is down!Observa cómo cada decorador añade su propio comportamiento mientras conserva la notificación original. El orden de envoltura determina el orden de salida: el componente más interno (BasicNotifier) se ejecuta primero y después cada decorador añade su capa. ¡Puedes combinar cualquier combinación de canales sin crear clases independientes para cada combinación!
Pruébalo tú mismo
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Lee el mensaje a enviar
String message = scanner.nextLine();
// Lee la lista de canales separados por comas (p. ej., "email,sms" o "push,email,sms")
String channelsInput = scanner.nextLine();
// TODO: Empieza con un BasicNotifier
// TODO: Divide la entrada de canales por coma
// TODO: Recorre cada canal y envuelve el notificador con el decorador apropiado
// Los canales válidos son: "email", "sms", "push"
// TODO: Llama a send en el notificador completamente decorado con el mensaje
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosIntroducción a la OOPClases vs. ObjetosLa palabra clave thisMétodosCampos (Atributos)Método constructorSobrecarga de constructoresResumen - Calculadora simple4Herencia
Herencia básica (extends)La palabra clave superSobrescritura de métodos (@Override)Encadenamiento de constructoresLa clase ObjectHerencia simple y multinivelPor qué no hay herencia múltiple de clasesResumen - Jerarquía de empleados7Métodos especiales y la clase Object
Método toString()equals() y hashCode()Método clone()compareTo() y ComparableInterfaz ComparatorResumen - Ordenamiento personalizado2Modificadores de acceso y encapsulamiento
Visión general de los niveles de accesoMétodos Getter y SetterOcultamiento de informaciónLa palabra clave finalResumen - Gestor de cuentas bancarias5Polimorfismo
Fundamentos de sobrecarga de métodosSobrescritura de métodos (tiempo de ejecución)Upcasting y DowncastingEl operador instanceofClases y métodos abstractosResumen - Calculadora de figuras8Conceptos avanzados de POO
Composición vs. HerenciaAgregación vs. ComposiciónClases internas, anidadas y anónimasEnums y métodos de EnumRecords (Java 16+)Clases selladas (Java 17+)11Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón BuilderPatrón ObserverPatrón Strategy3Propiedades de clase y miembros estáticos
Variables de instancia vs. estáticasMétodos estáticosBloques estáticosConstantes (static final)Repaso - Contador y utilidad6Interfaces y Clases Abstractas
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesMétodos Default y Static en InterfacesClases Abstractas vs InterfacesInterfaces FuncionalesResumen - Sistema de Pagos9Genéricos
Introducción a los genéricosClases genéricasMétodos genéricosParámetros de tipo delimitadosComodines (?, extends, super)Resumen - Contenedor genérico12Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón IteratorPractica por tu cuenta: Compilador de Java online