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 aos objetos dinamicamente, envolvendo-os em objetos especiais chamados decoradores. Diferentemente 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 encapsulado:
#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; }
};Os decoradores podem ser empilhados — cada um envolve o anterior, acrescentando funcionalidades 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 precisar adicionar responsabilidades a objetos dinamicamente sem afetar outros objetos ou quando estender a funcionalidade por meio de subclassificação resultar em uma explosão de classes.
Desafio
FácilVamos criar um Sistema de Pedidos de Pizza usando o padrão Decorator. Você criará um sistema flexível no qual os clientes podem começar com uma pizza básica 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: Defina a interface do componente e a classe da pizza básica.Crie uma classe abstrata
Pizzacom os métodos virtuais purosgetDescription()(retorna uma string) egetCost()(retorna um double), além de um destrutor virtual.Em seguida, crie uma classe concreta
PlainPizzaque represente a pizza básica. Ela deve retornar"Pizza"como descrição e ter um custo básico de8.0.ToppingDecorator.h: Crie a classe base do decorator e os decorators concretos de coberturas.Crie uma classe
ToppingDecoratorque herde dePizzae mantenha umstd::unique_ptr<Pizza>para o componente envolvido. Ela servirá como base para todos os decorators de coberturas.Implemente três decorators concretos de coberturas:
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, em seguida, adicionar sua própria contribuição.
main.cpp: Crie 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
PlainPizzae, em seguida, envolva-a com os decorators de coberturas apropriados com base nas entradas (na ordem: queijo, pepperoni, cogumelos). Por fim, 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 adicionar facilmente novas coberturas criando novas classes de decorator sem modificar o código existente da pizza ou das coberturas: essa é a beleza do padrão Decorator.
Experimente você mesmo
#include <iostream>
#include <string>
#include <memory>
#include "Pizza.h"
#include "ToppingDecorator.h"
using namespace std;
int main() {
// Leia 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 POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online