Menu
Coddy logo textTech

Patrón Decorator

Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 98 de 104.

El patrón Decorator te permite añadir nuevos comportamientos a los objetos dinámicamente envolviéndolos en objetos especiales llamados decoradores. A diferencia de la herencia, que añade comportamiento en tiempo de compilación, los decoradores te permiten ampliar la funcionalidad en tiempo de ejecución sin modificar la clase original.

El patrón funciona haciendo que tanto el objeto original como los decoradores implementen la misma interfaz. Cada decorador contiene una referencia a un componente y añade su propio comportamiento antes o después de delegar en el objeto envuelto:

#include <iostream>
#include <memory>

// Interfaz del componente
class Coffee {
public:
    virtual std::string getDescription() const = 0;
    virtual double getCost() const = 0;
    virtual ~Coffee() = default;
};

// Componente concreto
class SimpleCoffee : public Coffee {
public:
    std::string getDescription() const override { return "Coffee"; }
    double getCost() const override { return 2.0; }
};

// Decorador base
class CoffeeDecorator : public Coffee {
protected:
    std::unique_ptr<Coffee> coffee;
public:
    CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}
};

// Decoradores concretos
class MilkDecorator : public CoffeeDecorator {
public:
    MilkDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
    std::string getDescription() const override {
        return coffee->getDescription() + ", Milk";
    }
    double getCost() const override { return coffee->getCost() + 0.5; }
};

class SugarDecorator : public CoffeeDecorator {
public:
    SugarDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
    std::string getDescription() const override {
        return coffee->getDescription() + ", Sugar";
    }
    double getCost() const override { return coffee->getCost() + 0.2; }
};

Los decoradores se pueden apilar: cada uno envuelve al anterior, acumulando funcionalidad capa por capa:

int main() {
    std::unique_ptr<Coffee> order = std::make_unique<SimpleCoffee>();
    order = std::make_unique<MilkDecorator>(std::move(order));
    order = std::make_unique<SugarDecorator>(std::move(order));
    
    std::cout << order->getDescription() << ": $" << order->getCost();
    // Salida: Coffee, Milk, Sugar: $2.7
}

Usa Decorator cuando necesites añadir responsabilidades a los objetos de forma dinámica sin afectar a otros objetos, o cuando ampliar la funcionalidad mediante la creación de subclases provocaría una proliferación de clases.

challenge icon

Desafío

Fácil

Construyamos un Sistema de pedidos de Pizza utilizando el patrón Decorator. Crearás un sistema flexible en el que los clientes pueden comenzar con una pizza base y añadir varios ingredientes: cada ingrediente envuelve el pedido anterior y añade su propio costo y descripción. Este es un caso de uso perfecto para los decoradores: los ingredientes pueden combinarse en cualquier orden sin crear una explosión de subclases.

Organizarás tu código en tres archivos:

  • Pizza.h: Define la interfaz del componente y la clase de pizza base.

    Crea una clase abstracta Pizza con los métodos virtuales puros getDescription() (devuelve un string) y getCost() (devuelve un double), además de un destructor virtual.

    Después crea una clase concreta PlainPizza que represente la pizza base. Debe devolver "Pizza" como descripción y tener un costo base de 8.0.

  • ToppingDecorator.h: Crea la clase base del decorador y los decoradores concretos de ingredientes.

    Crea una clase ToppingDecorator que herede de Pizza y contenga un std::unique_ptr<Pizza> hacia el componente envuelto. Esta sirve como base para todos los decoradores de ingredientes.

    Implementa tres decoradores concretos de ingredientes:

    • CheeseTopping: añade ", Cheese" a la descripción y 1.5 al costo
    • PepperoniTopping: añade ", Pepperoni" a la descripción y 2.0 al costo
    • MushroomTopping: añade ", Mushrooms" a la descripción y 1.0 al costo

    Cada decorador debe delegar en la pizza envuelta y después añadir su propia contribución.

  • main.cpp: Crea un pedido de pizza personalizado según la entrada del usuario.

    Lee tres entradas (cada una será yes o no):

    1. ¿Añadir queso?
    2. ¿Añadir pepperoni?
    3. ¿Añadir champiñones?

    Comienza con una PlainPizza y después envuélvela con los decoradores de ingredientes correspondientes según las entradas (en este orden: queso, pepperoni, champiñones). Finalmente, imprime los detalles del pedido con este formato:

    Order: [description]
    Total: $[cost]

Por ejemplo, con las entradas yes, yes, no:

Order: Pizza, Cheese, Pepperoni
Total: $11.5

Con las entradas yes, no, yes:

Order: Pizza, Cheese, Mushrooms
Total: $10.5

Con las entradas no, no, no:

Order: Pizza
Total: $8

Con las entradas yes, yes, yes:

Order: Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.5

Observa cómo cada decorador envuelve al anterior, construyendo tanto la descripción como el costo capa por capa. Puedes añadir fácilmente nuevos ingredientes creando nuevas clases decoradoras sin modificar el código existente de la pizza o de los ingredientes: esa es la belleza del patrón Decorator.

Pruébalo tú mismo

#include <iostream>
#include <string>
#include <memory>
#include "Pizza.h"
#include "ToppingDecorator.h"

using namespace std;

int main() {
    // Lee tres entradas (yes o no)
    string addCheese, addPepperoni, addMushrooms;
    cin >> addCheese;
    cin >> addPepperoni;
    cin >> addMushrooms;
    
    // TODO: Comienza con un PlainPizza usando std::unique_ptr
    
    // TODO: Si addCheese es "yes", envuelve la pizza con CheeseTopping
    
    // TODO: Si addPepperoni es "yes", envuelve la pizza con PepperoniTopping
    
    // TODO: Si addMushrooms es "yes", envuelve la pizza con MushroomTopping
    
    // TODO: Imprime el pedido en el formato:
    // Order: [description]
    // Total: $[cost]
    
    return 0;
}
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

Practica por tu cuenta: Compilador de C++ online