Паттерн Декоратор
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 58 из 70.
Паттерн Декоратор — это структурный паттерн, который позволяет добавлять объектам новые обязанности, оборачивая их в специальные объекты-обертки, называемые декораторами. В отличие от наследования, которое добавляет поведение на этапе компиляции, декораторы расширяют функциональность динамически во время выполнения, не изменяя исходный класс.
Паттерн работает за счет того, что декораторы реализуют тот же интерфейс, что и объект, который они оборачивают. Каждый декоратор хранит ссылку на обернутый объект и делегирует ему вызовы, добавляя собственное поведение до или после:
public interface ICoffee
{
string GetDescription();
decimal GetCost();
}
public class SimpleCoffee : ICoffee
{
public string GetDescription() => "Coffee";
public decimal GetCost() => 2.00m;
}
public abstract class CoffeeDecorator : ICoffee
{
protected ICoffee _coffee;
public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
public virtual string GetDescription() => _coffee.GetDescription();
public virtual decimal GetCost() => _coffee.GetCost();
}
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription() => _coffee.GetDescription() + ", Milk";
public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}Сила декораторов заключается в их способности к наслоению. Вы можете обернуть декорированный объект еще одним декоратором:
ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee); // Двойное молоко!
Console.WriteLine(coffee.GetDescription()); // Кофе, Молоко, Молоко
Console.WriteLine(coffee.GetCost()); // 3.00Паттерн Декоратор идеален, когда вам нужно добавить объектам новые обязанности без создания огромного количества подклассов. Каждый декоратор фокусируется на одной задаче, и вы комбинируете их по мере необходимости, чтобы сформировать именно то поведение, которое вам нужно.
Задание
ЛегкоДавайте создадим систему заказа пиццы, используя паттерн Декоратор. Вы создадите базовую пиццу, а затем будете динамически добавлять к ней топпинги, где каждый топпинг оборачивает предыдущий слой, дополняя описание и увеличивая цену.
Вы организуете свой код в трех файлах:
Pizza.cs: Определите интерфейсIPizzaв пространстве именPizzaShopс двумя методами:GetDescription(), возвращающим строку, иGetPrice(), возвращающим десятичное число (decimal). Затем создайте классPlainPizza, который реализует этот интерфейс — он должен возвращать"Pizza"в качестве описания и8.00mв качестве базовой цены.ToppingDecorators.cs: Создайте абстрактный классToppingDecorator, который реализуетIPizzaи хранит ссылку на оборачиваемую пиццу (передаваемую через конструктор). Его виртуальные методы должны делегировать выполнение обернутой пицце. Затем создайте три конкретных декоратора:CheeseTopping— добавляет", Cheese"к описанию и прибавляет1.50mк ценеPepperoniTopping— добавляет", Pepperoni"к описанию и прибавляет2.00mк ценеMushroomTopping— добавляет", Mushrooms"к описанию и прибавляет1.25mк цене
Program.cs: Объедините все вместе, начав с обычной пиццы и оборачивая ее топпингами на основе входных данных. Каждый декоратор топпинга оборачивает предыдущую пиццу, формируя окончательный заказ слой за слоем.
Вы получите одни входные данные:
- Список топпингов для добавления, разделенных запятыми (например,
cheese,pepperoni,cheese)
Начните с PlainPizza, затем оберните ее соответствующим декоратором для каждого топпинга в том порядке, в котором они появляются. Допустимые топпинги: cheese, pepperoni и mushrooms. После применения всех топпингов выведите описание на одной строке и общую стоимость на следующей.
Например, если на вход подается cheese,pepperoni,mushrooms, вывод должен быть следующим:
Pizza, Cheese, Pepperoni, Mushrooms
12.75Обратите внимание, что один и тот же топпинг можно добавлять несколько раз! Если на вход подается cheese,cheese, вы получите:
Pizza, Cheese, Cheese
11Это демонстрирует гибкость паттерна декоратор — каждый декоратор оборачивает любую пиццу, которую он получает, будь то обычная пицца или уже украшенная, что позволяет динамически наращивать функциональность!
Шпаргалка
Паттерн Декоратор — это структурный паттерн, который позволяет динамически добавлять объектам новые обязанности, оборачивая их в специальные объекты-обертки. Он расширяет функциональность объекта во время выполнения, не изменяя исходный класс.
Декораторы реализуют тот же интерфейс, что и объект, который они оборачивают, хранят ссылку на этот объект и делегируют ему вызовы, добавляя при этом собственное поведение:
public interface ICoffee
{
string GetDescription();
decimal GetCost();
}
public class SimpleCoffee : ICoffee
{
public string GetDescription() => "Coffee";
public decimal GetCost() => 2.00m;
}
public abstract class CoffeeDecorator : ICoffee
{
protected ICoffee _coffee;
public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
public virtual string GetDescription() => _coffee.GetDescription();
public virtual decimal GetCost() => _coffee.GetCost();
}
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription() => _coffee.GetDescription() + ", Milk";
public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}Декораторы можно накладывать друг на друга, оборачивая уже декорированный объект в новый декоратор:
ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee); // Двойное молоко!
Console.WriteLine(coffee.GetDescription()); // Coffee, Milk, Milk
Console.WriteLine(coffee.GetCost()); // 3.00Используйте паттерн Декоратор, когда вам нужно добавить объектам новые обязанности без создания множества подклассов. Каждый декоратор фокусируется на одной задаче и может быть скомбинирован с другими по мере необходимости.
Попробуйте сами
using System;
using PizzaShop;
class Program
{
public static void Main(string[] args)
{
// Прочитать список топпингов, разделенных запятыми
string input = Console.ReadLine();
string[] toppings = input.Split(',');
// TODO: Начать с PlainPizza
// TODO: Пройтись циклом по каждому топпингу и обернуть пиццу
// соответствующим декоратором на основе названия топпинга:
// - "cheese" -> CheeseTopping
// - "pepperoni" -> PepperoniTopping
// - "mushrooms" -> MushroomTopping
// TODO: Вывести итоговое описание и цену
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методов