Padrão Decorator
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 98 de 104.
O padrão Decorator permite adicionar novos comportamentos a objetos dinamicamente, envolvendo-os em objetos especiais chamados decoradores. Ao contrário da herança, que adiciona comportamento em tempo de compilação, os decoradores permitem estender a funcionalidade em tempo de execução sem modificar a classe original.
O padrão funciona fazendo com que tanto o objeto original quanto os decoradores implementem a mesma interface. Cada decorador mantém uma referência a um componente e adiciona seu próprio comportamento antes ou depois de delegar para o objeto envolvido:
#include <iostream>
#include <memory>
// Interface do 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; }
};Decoradores podem ser empilhados - cada um envolve o anterior, construindo a funcionalidade camada por camada:
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();
// Saída: Coffee, Milk, Sugar: $2.7
}Use o Decorator quando você precisar adicionar responsabilidades a objetos dinamicamente sem afetar outros objetos, ou quando estender a funcionalidade através de subclasses resultaria em uma explosão de classes.
Desafio
FácilVamos construir um Sistema de Pedidos de Pizza usando o padrão Decorator. Você criará um sistema flexível onde os clientes podem começar com uma pizza base e adicionar várias coberturas — cada cobertura envolve o pedido anterior e adiciona seu próprio custo e descrição. Este é um caso de uso perfeito para decorators: as coberturas podem ser empilhadas em qualquer combinação sem criar uma explosão de subclasses.
Você organizará seu código em três arquivos:
Pizza.h: Define a interface do componente e a classe base da pizza.Crie uma classe abstrata
Pizzacom métodos virtuais purosgetDescription()(retorna uma string) egetCost()(retorna um double), além de um destrutor virtual.Em seguida, crie uma classe concreta
PlainPizzaque representa a pizza base. Ela deve retornar"Pizza"como sua descrição e ter um custo base de8.0.ToppingDecorator.h: Constrói a classe base do decorator e os decorators de cobertura concretos.Crie uma classe
ToppingDecoratorque herda dePizzae contém umstd::unique_ptr<Pizza>para o componente envolvido. Isso serve como base para todos os decorators de cobertura.Implemente três decorators de cobertura concretos:
CheeseTopping— adiciona", Cheese"à descrição e1.5ao custoPepperoniTopping— adiciona", Pepperoni"à descrição e2.0ao custoMushroomTopping— adiciona", Mushrooms"à descrição e1.0ao custo
Cada decorator deve delegar para a pizza envolvida e então adicionar sua própria contribuição.
main.cpp: Cria um pedido de pizza personalizado com base na entrada do usuário.Leia três entradas (cada uma será
yesouno):- Adicionar queijo?
- Adicionar pepperoni?
- Adicionar cogumelos?
Comece com uma
PlainPizza, então envolva-a com os decorators de cobertura apropriados com base nas entradas (na ordem: queijo, pepperoni, cogumelos). Finalmente, imprima os detalhes do pedido neste formato:Order: [description] Total: $[cost]
Por exemplo, com as entradas yes, yes, no:
Order: Pizza, Cheese, Pepperoni
Total: $11.5Com as entradas yes, no, yes:
Order: Pizza, Cheese, Mushrooms
Total: $10.5Com as entradas no, no, no:
Order: Pizza
Total: $8Com as entradas yes, yes, yes:
Order: Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.5Observe como cada decorator envolve o anterior, construindo tanto a descrição quanto o custo camada por camada. Você pode facilmente adicionar novas coberturas criando novas classes decorator sem modificar o código da pizza ou das coberturas existentes — essa é a beleza do padrão Decorator.
Folha de consulta
O padrão Decorator permite adicionar novos comportamentos a objetos dinamicamente, envolvendo-os em objetos especiais chamados decoradores. Ao contrário da herança, que adiciona comportamento em tempo de compilação, os decoradores permitem estender a funcionalidade em tempo de execução sem modificar a classe original.
O padrão funciona fazendo com que tanto o objeto original quanto os decoradores implementem a mesma interface. Cada decorador mantém uma referência a um componente e adiciona seu próprio comportamento antes ou depois de delegar ao objeto envolvido.
Estrutura
Interface do Componente: Define a interface comum tanto para componentes concretos quanto para decoradores.
class Coffee {
public:
virtual std::string getDescription() const = 0;
virtual double getCost() const = 0;
virtual ~Coffee() = default;
};Componente concreto: O objeto base que pode ser decorado.
class SimpleCoffee : public Coffee {
public:
std::string getDescription() const override { return "Coffee"; }
double getCost() const override { return 2.0; }
};Decorador base: Mantém uma referência a um componente e implementa a mesma interface.
class CoffeeDecorator : public Coffee {
protected:
std::unique_ptr<Coffee> coffee;
public:
CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}
};Decoradores concretos: Adicionam comportamentos específicos delegando ao objeto envolvido e adicionando sua própria funcionalidade.
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; }
};Empilhando Decoradores
Os decoradores podem ser empilhados - cada um envolve o anterior, construindo a funcionalidade camada por camada:
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();
// Saída: Coffee, Milk, Sugar: $2.7Quando Usar
Use o Decorator quando precisar adicionar responsabilidades a objetos dinamicamente sem afetar outros objetos, ou quando a extensão da funcionalidade por meio de subclasses resultaria em uma explosão de classes.
Experimente você mesmo
#include <iostream>
#include <string>
#include <memory>
#include "Pizza.h"
#include "ToppingDecorator.h"
using namespace std;
int main() {
// Lê três entradas (yes ou no)
string addCheese, addPepperoni, addMushrooms;
cin >> addCheese;
cin >> addPepperoni;
cin >> addMushrooms;
// TODO: Comece com uma PlainPizza usando std::unique_ptr
// TODO: Se addCheese for "yes", envolva a pizza com CheeseTopping
// TODO: Se addPepperoni for "yes", envolva a pizza com PepperoniTopping
// TODO: Se addMushrooms for "yes", envolva a pizza com MushroomTopping
// TODO: Imprima o pedido no formato:
// Order: [description]
// Total: $[cost]
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas11Conceitos Avançados de POO
Composição vs HerançaMixins via CRTPPimpl IdiomType ErasureEnum Classes e Tipagem ForteTratamento de Exceções em POOHierarquias de Exceções Personalizadas14Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como um Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico