Паттерн Декоратор
Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 96 из 110.
Паттерн Декоратор позволяет динамически добавлять объектам новые обязанности, оборачивая их в специальные объекты, называемые декораторами. В отличие от наследования, которое добавляет поведение на этапе компиляции, декораторы позволяют расширять функциональность объектов во время выполнения, не изменяя исходный класс.
Основная идея заключается в том, что и оригинальный объект, и декораторы реализуют один и тот же интерфейс.
Декоратор оборачивает оригинальный объект, делегирует ему выполнение и добавляет собственное поведение до или после:
abstract class Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
@override
String getDescription() => 'Simple Coffee';
@override
double getCost() => 2.0;
}
class MilkDecorator implements Coffee {
final Coffee _coffee;
MilkDecorator(this._coffee);
@override
String getDescription() => '${_coffee.getDescription()}, Milk';
@override
double getCost() => _coffee.getCost() + 0.5;
}
class SugarDecorator implements Coffee {
final Coffee _coffee;
SugarDecorator(this._coffee);
@override
String getDescription() => '${_coffee.getDescription()}, Sugar';
@override
double getCost() => _coffee.getCost() + 0.2;
}
void main() {
Coffee coffee = SimpleCoffee();
coffee = MilkDecorator(coffee);
coffee = SugarDecorator(coffee);
print(coffee.getDescription()); // Simple Coffee, Milk, Sugar
print(coffee.getCost()); // 2.7
}Каждый декоратор оборачивает предыдущий объект и расширяет его возможности. Вы можете комбинировать несколько декораторов в любом порядке, создавая гибкое поведение без необходимости создания подкласса для каждой возможной комбинации. Этот паттерн идеально подходит, когда вам нужно добавить функциональность объектам, не затрагивая другие объекты того же класса.
Задание
ЛегкоДавайте создадим систему заказа пиццы, используя паттерн Декоратор! Вы создадите базовую пиццу, которую можно дополнять различными начинками, где каждая начинка оборачивает предыдущий элемент и увеличивает как описание, так и цену.
Вы организуете свой код в двух файлах:
pizza.dart: Этот файл содержит ваш интерфейс пиццы и все классы-декораторы. Начните с абстрактного классаPizza, который определяет два метода:String getDescription()иdouble getPrice(). Затем создайте классPlainPizza, который реализуетPizza— он возвращаетPlain Pizzaв качестве описания и стоит8.0. Теперь создайте три класса-декоратора, каждый из которых оборачиваетPizzaи дополняет её:CheeseDecorator— добавляет, Cheeseк описанию и1.5к ценеPepperoniDecorator— добавляет, Pepperoniк описанию и2.0к ценеMushroomDecorator— добавляет, Mushroomsк описанию и1.25к цене
Pizza, приниматьPizzaв своем конструкторе и делегировать выполнение обернутой пицце перед добавлением собственного вклада.main.dart: Импортируйте файл с пиццей и продемонстрируйте, как декораторы накладываются друг на друга для создания индивидуального заказа. Начните сPlainPizza, затем оберните её вCheeseDecorator, затем оберните результат вPepperoniDecoratorи, наконец, оберните всё вMushroomDecorator. Выведите описание итоговой пиццы, а затем выведитеTotal: $, за которым следует цена.
Обратите внимание, как каждый декоратор строится на основе предыдущего — вы создаете пиццу со всеми начинками, накладывая простые декораторы с единственной ответственностью!
Ожидаемый результат:
Plain Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.75Шпаргалка
Паттерн Декоратор динамически добавляет объектам новые обязанности, оборачивая их в объекты-декораторы. Как исходный объект, так и декораторы реализуют один и тот же интерфейс.
Ключевые компоненты:
- Абстрактный класс или интерфейс, определяющий общие методы
- Конкретный класс, реализующий базовую функциональность
- Классы декораторов, которые оборачивают исходный объект и расширяют его
Каждый декоратор:
- Реализует тот же интерфейс, что и декорируемый объект
- Хранит ссылку на обернутый объект
- Делегирует вызовы обернутому объекту
- Добавляет собственное поведение до или после делегирования
abstract class Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
@override
String getDescription() => 'Simple Coffee';
@override
double getCost() => 2.0;
}
class MilkDecorator implements Coffee {
final Coffee _coffee;
MilkDecorator(this._coffee);
@override
String getDescription() => '${_coffee.getDescription()}, Milk';
@override
double getCost() => _coffee.getCost() + 0.5;
}
// Наслоение нескольких декораторов
Coffee coffee = SimpleCoffee();
coffee = MilkDecorator(coffee);
coffee = SugarDecorator(coffee);Декораторы можно комбинировать в любом порядке, создавая гибкое поведение без необходимости создания подклассов для каждой возможной комбинации.
Попробуйте сами
import 'pizza.dart';
void main() {
// TODO: Создать PlainPizza
// TODO: Обернуть в CheeseDecorator
// TODO: Обернуть это в PepperoniDecorator
// TODO: Обернуть всё в MushroomDecorator
// TODO: Вывести описание итоговой пиццы
// TODO: Вывести "Total: $" и цену
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыБиблиотеки и импортВведение в ООПКлассы и объектыКлючевое слово thisМетодыПеременные экземпляраОсновы конструкторовИтоги — Простой калькулятор4Null-безопасность
Введение в Null-безопасностьNullable и Non-Nullable типыОператоры ? и !Ключевое слово Late и Null-безопасностьNull-aware операторыNull-безопасность в классахИтоги — Система профилей пользователей7Абстрактные классы и интерфейсы
Абстрактные классыАбстрактные методыИнтерфейсы в DartНеявные интерфейсыРеализация против наследованияМножественные интерфейсыИтоги — Калькулятор фигур10Коллекции и обобщения
Обзор List, Set, MapТипобезопасные коллекцииОбобщенные классыОбобщенные методыОграничения обобщенийIterable и IteratorИтоги: Обобщенное хранилище13Продвинутые концепции ООП
Композиция против наследованияМетоды расширенияВызываемые классыЗапечатанные классы (Dart 3)Записи (Dart 3)Паттерны и сопоставление (3.0)Перечисления с методами16Проект: Управление библиотекой
Обзор проектаКлассы Book и User2Конструкторы в Dart
Конструктор по умолчаниюИменованные конструкторыСписки инициализацииКонстантные конструкторыФабричные конструкторыПеренаправляющие конструкторыИтоги — Shape Builder5Инкапсуляция
Публичные и приватные членыСоглашение о префиксе _Приватность на уровне библиотекГеттеры и сеттеры: подробный разборСкрытие данныхИтоги — Записи о студентах8Mixins
Введение в MixinsСоздание MixinsИспользование нескольких MixinsКлючевое слово on в MixinsMixin и наследованиеMixin и интерфейсИтоги: Система животных11Специальные методы
Переопределение toString()Переопределение hashCode и ==Интерфейс ComparableМетод call()Переопределение noSuchMethodПовторение — Пользовательская коллекция14Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy3Свойства классов
Члены экземпляра и статические членыПоля Final и ConstПеременные LateСтатические методы и поляГеттеры и сеттерыИтоги — Менеджер банковского счета6Наследование
Основы наследованияКлючевое слово superПереопределение методовАннотация @overrideКлючевое слово final для классовКонструкторы и наследованиеИтоги: Иерархия сотрудников9Полиморфизм
Основы полиморфизмаПолиморфизм через интерфейсыПроверка типов во время выполненияОператоры is и asКлючевое слово covariantИтоги — Обработчик платежей12Асинхронное ООП
Futures и async/awaitОсновы StreamsКонтроллеры StreamАсинхронные конструкторыАсинхронность в методах классовИтоги — Data Fetcher15Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий