Menu
Coddy logo textTech

Паттерн Декоратор

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 98 из 104.

Паттерн Decorator позволяет динамически добавлять новые возможности объектам, оборачивая их в специальные объекты, называемые decorators. В отличие от наследования, которое добавляет поведение во время компиляции, decorators позволяют расширять функциональность во время выполнения, не изменяя исходный класс.

Шаблон работает за счёт того, что и исходный объект, и decorator реализуют один и тот же interface. Каждый decorator хранит ссылку на component и добавляет собственное поведение до или после делегирования вызова обёрнутому объекту:

#include <iostream>
#include <memory>

// Интерфейс компонента
class Coffee {
public:
    virtual std::string getDescription() const = 0;
    virtual double getCost() const = 0;
    virtual ~Coffee() = default;
};

// Конкретный компонент
class SimpleCoffee : public Coffee {
public:
    std::string getDescription() const override { return "Coffee"; }
    double getCost() const override { return 2.0; }
};

// Базовый декоратор
class CoffeeDecorator : public Coffee {
protected:
    std::unique_ptr<Coffee> coffee;
public:
    CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}
};

// Конкретные декораторы
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; }
};

Декораторы можно объединять в цепочку — каждый оборачивает предыдущий, постепенно наращивая функциональность слой за слоем:

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();
    // Вывод: Coffee, Milk, Sugar: $2.7
}

Используйте Decorator, когда нужно динамически добавлять объектам обязанности, не затрагивая другие объекты, или когда расширение функциональности с помощью создания подклассов привело бы к взрывному росту числа классов.

challenge icon

Задание

Легко

Давайте создадим Pizza Order System с использованием шаблона Decorator. Вы создадите гибкую систему, в которой клиенты могут начать с базовой пиццы и добавить различные топпинги: каждый топпинг оборачивает предыдущий заказ и добавляет собственную стоимость и описание. Это идеальный вариант использования decorators: топпинги можно комбинировать в любом порядке, не создавая множество подклассов.

Вы организуете код в трёх файлах:

  • Pizza.h: определите интерфейс компонента и базовый класс пиццы.

    Создайте абстрактный Pizza class с чистыми виртуальными методами getDescription() (возвращает строку) и getCost() (возвращает double), а также с виртуальным деструктором.

    Затем создайте concrete PlainPizza class, представляющий базовую пиццу. Он должен возвращать "Pizza" в качестве описания и иметь базовую стоимость 8.0.

  • ToppingDecorator.h: создайте базовый class decorator и concrete decorators для топпингов.

    Создайте class ToppingDecorator, который наследуется от Pizza и хранит std::unique_ptr<Pizza> оборачиваемого компонента. Он служит базовым class для всех decorators топпингов.

    Implement три concrete decorator топпингов:

    • CheeseTopping: добавляет ", Cheese" к описанию и 1.5 к стоимости
    • PepperoniTopping: добавляет ", Pepperoni" к описанию и 2.0 к стоимости
    • MushroomTopping: добавляет ", Mushrooms" к описанию и 1.0 к стоимости

    Каждый decorator должен делегировать вызов обёрнутой пицце, а затем добавлять собственный вклад.

  • main.cpp: создайте настраиваемый заказ пиццы на основе пользовательского ввода.

    Считайте три значения (каждое будет равно yes или no):

    1. Добавить сыр?
    2. Добавить пепперони?
    3. Добавить грибы?

    Начните с PlainPizza, затем оберните её соответствующими decorators топпингов на основе введённых значений (в следующем порядке: сыр, пепперони, грибы). Наконец, выведите сведения о заказе в следующем формате:

    Order: [description]
    Total: $[cost]

Например, при значениях yes, yes, no:

Order: Pizza, Cheese, Pepperoni
Total: $11.5

При значениях yes, no, yes:

Order: Pizza, Cheese, Mushrooms
Total: $10.5

При значениях no, no, no:

Order: Pizza
Total: $8

При значениях yes, yes, yes:

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

Обратите внимание, как каждый decorator оборачивает предыдущий, постепенно формируя описание и стоимость. Вы легко можете добавить новые топпинги, создав новые decorator classes, не изменяя существующий код пиццы или топпингов: в этом и заключается преимущество шаблона Decorator.

Попробуйте сами

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

using namespace std;

int main() {
    // Считать три ввода (yes или no)
    string addCheese, addPepperoni, addMushrooms;
    cin >> addCheese;
    cin >> addPepperoni;
    cin >> addMushrooms;
    
    // TODO: Начните с PlainPizza, используя std::unique_ptr
    
    // TODO: Если addCheese равно "yes", оберните пиццу в CheeseTopping
    
    // TODO: Если addPepperoni равно "yes", оберните пиццу в PepperoniTopping
    
    // TODO: Если addMushrooms равно "yes", оберните пиццу в MushroomTopping
    
    // TODO: Выведите заказ в формате:
    // Order: [description]
    // Total: $[cost]
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор C++