Паттерн Декоратор
Часть раздела Объектно-ориентированное программирование путешествия по 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, когда нужно динамически добавлять объектам обязанности, не затрагивая другие объекты, или когда расширение функциональности с помощью создания подклассов привело бы к взрывному росту числа классов.
Задание
ЛегкоДавайте создадим Pizza Order System с использованием шаблона Decorator. Вы создадите гибкую систему, в которой клиенты могут начать с базовой пиццы и добавить различные топпинги: каждый топпинг оборачивает предыдущий заказ и добавляет собственную стоимость и описание. Это идеальный вариант использования decorators: топпинги можно комбинировать в любом порядке, не создавая множество подклассов.
Вы организуете код в трёх файлах:
Pizza.h: определите интерфейс компонента и базовый класс пиццы.Создайте абстрактный
Pizzaclass с чистыми виртуальными методамиgetDescription()(возвращает строку) иgetCost()(возвращает double), а также с виртуальным деструктором.Затем создайте concrete
PlainPizzaclass, представляющий базовую пиццу. Он должен возвращать"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):- Добавить сыр?
- Добавить пепперони?
- Добавить грибы?
Начните с
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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++