Menu
Coddy logo textTech

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 Decorador (Decorator Pattern) es un patrón de diseño estructural que 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 comienzas con un café básico y puedes añadir extras como leche, azúcar o crema batida. Cada adición envuelve al café original, añadiendo su costo y descripción. El patrón utiliza una interfaz común para que los objetos decorados puedan ser tratados de la misma manera 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 le delega las llamadas mientras añade su propio comportamiento. Puedes apilar múltiples decoradores para combinar funcionalidades:

Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);

System.out.println(order.getDescription());  // Café sencillo, leche, azúcar
System.out.println(order.getCost());         // 2.75

El 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 mezclarlos y combinarlos libremente para crear exactamente la combinación de comportamientos que necesites.

challenge icon

Desafío

Fácil

¡Vamos a construir un sistema de notificaciones utilizando 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 con otros. Esto refleja los sistemas del mundo real donde un solo mensaje puede necesitar salir a través de múltiples canales simultáneamente.

Organizarás tu código en cuatro archivos:

  • Notifier.java: Define la interfaz Notifier que sirve como la interfaz del componente para tu patrón decorador. Debe declarar un método send(String message) que maneje el envío de notificaciones.
  • BasicNotifier.java: Crea la clase de componente concreto que implementa Notifier. Esto representa la forma más simple de notificación: una alerta dentro de la aplicación (in-app). Cuando se llama a send, debe imprimir In-App: [message].
  • NotifierDecorators.java: Construye aquí tu jerarquía de decoradores. Comienza con una clase abstracta NotifierDecorator que implemente Notifier y mantenga una referencia a un objeto Notifier envuelto (pasado a través de su constructor).

    Luego crea tres decoradores concretos que extiendan NotifierDecorator:

    EmailDecorator - Cuando se llama a send, primero delega en el notificador envuelto, luego imprime Email: [message].

    SMSDecorator - Cuando se llama a send, primero delega en el notificador envuelto, luego imprime SMS: [message].

    PushDecorator - Cuando se llama a send, primero delega en el notificador envuelto, luego imprime Push: [message].

  • Main.java: ¡Une tu sistema de decoradores! Recibirás dos entradas: el mensaje de notificación (String) y una lista de canales para agregar separada por comas (String), por ejemplo "email,sms" o "push,email,sms".

    Comienza con un BasicNotifier. Luego, basándote en la entrada de canales, envuélvelo con los decoradores apropiados en el orden en que aparecen. Los nombres de canales válidos son email, sms y push.

    Finalmente, llama a send con tu mensaje en el notificador completamente decorado.

Recibirás dos entradas en orden: el mensaje a enviar (String) y los canales a habilitar (String, separados por comas).

Por ejemplo, con las entradas Server is down! y email,push, tu salida 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 preserva la notificación original. El orden de envoltura determina el orden de la salida: el componente más interno (BasicNotifier) se ejecuta primero, luego cada decorador añade su capa. ¡Puedes mezclar y combinar cualquier combinación de canales sin crear clases separadas para cada combinación!

Hoja de referencia

El Patrón Decorador es un patrón de diseño estructural que añade nuevos comportamientos a los objetos envolviéndolos en objetos decoradores. Añade funcionalidad dinámicamente en tiempo de ejecución sin modificar la clase original.

El patrón utiliza una interfaz común para que los objetos decorados puedan ser tratados de la misma manera que el objeto original.

Estructura Básica

Define una interfaz de componente:

interface Coffee {
    String getDescription();
    double getCost();
}

Crea un componente concreto que implemente la interfaz:

class SimpleCoffee implements Coffee {
    public String getDescription() {
        return "Simple Coffee";
    }
    public double getCost() {
        return 2.0;
    }
}

Crea una clase decoradora abstracta que implemente la misma interfaz y mantenga una referencia al objeto envuelto:

abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;
    
    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
}

Crea decoradores concretos que extiendan el decorador abstracto:

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;
    }
}

Uso de Decoradores

Apila múltiples decoradores para combinar características:

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.75

El decorador delega las llamadas al objeto envuelto mientras añade su propio comportamiento. Cada decorador es independiente y puede mezclarse y combinarse libremente.

Pruébalo tú mismo

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Leer el mensaje a enviar
        String message = scanner.nextLine();
        
        // Leer la lista de canales separados por comas (ej., "email,sms" o "push,email,sms")
        String channelsInput = scanner.nextLine();
        
        // TODO: Comenzar con un BasicNotifier
        
        // TODO: Dividir la entrada de canales por coma
        
        // TODO: Recorrer cada canal y envolver el notificador con el decorador apropiado
        // Los canales válidos son: "email", "sms", "push"
        
        // TODO: Llamar a send en el notificador completamente decorado con el mensaje
        
    }
}
quiz iconPonte a prueba

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