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.
Desafío
FácilConstruyamos 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
Pizzacon los métodos virtuales purosgetDescription()(devuelve un string) ygetCost()(devuelve un double), además de un destructor virtual.Después crea una clase concreta
PlainPizzaque represente la pizza base. Debe devolver"Pizza"como descripción y tener un costo base de8.0.ToppingDecorator.h: Crea la clase base del decorador y los decoradores concretos de ingredientes.Crea una clase
ToppingDecoratorque herede dePizzay contenga unstd::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 y1.5al costoPepperoniTopping: añade", Pepperoni"a la descripción y2.0al costoMushroomTopping: añade", Mushrooms"a la descripción y1.0al 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á
yesono):- ¿Añadir queso?
- ¿Añadir pepperoni?
- ¿Añadir champiñones?
Comienza con una
PlainPizzay 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.5Con las entradas yes, no, yes:
Order: Pizza, Cheese, Mushrooms
Total: $10.5Con las entradas no, no, no:
Order: Pizza
Total: $8Con las entradas yes, yes, yes:
Order: Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.5Observa 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;
}
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 externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genéricoPractica por tu cuenta: Compilador de C++ online