Menu
Coddy logo textTech

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.

challenge icon

Desafio

Fácil

Vamos 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 Pizza com os métodos virtuais puros getDescription() (retorna uma string) e getCost() (retorna um double), além de um destrutor virtual.

    Em seguida, crie uma classe concreta PlainPizza que represente a pizza básica. Ela deve retornar "Pizza" como descrição e ter um custo básico de 8.0.

  • ToppingDecorator.h: Crie a classe base do decorator e os decorators concretos de coberturas.

    Crie uma classe ToppingDecorator que herde de Pizza e mantenha um std::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 e 1.5 ao custo
    • PepperoniTopping: adiciona ", Pepperoni" à descrição e 2.0 ao custo
    • MushroomTopping: adiciona ", Mushrooms" à descrição e 1.0 ao 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á yes ou no):

    1. Adicionar queijo?
    2. Adicionar pepperoni?
    3. Adicionar cogumelos?

    Comece com uma PlainPizza e, 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.5

Com as entradas yes, no, yes:

Order: Pizza, Cheese, Mushrooms
Total: $10.5

Com as entradas no, no, no:

Order: Pizza
Total: $8

Com as entradas yes, yes, yes:

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

Observe 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;
}
quiz iconTeste seus conhecimentos

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

Pratique por conta própria: Compilador de C++ online