Паттерн Декоратор
Часть раздела Объектно-ориентированное программирование путешествия по 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()); // Coffee, Milk, Milk
Console.WriteLine(coffee.GetCost()); // 3.00Паттерн Декоратор (Decorator) идеален, когда вам нужно добавить объектам новые обязанности без создания огромного количества подклассов. Каждый декоратор фокусируется на одной задаче, и вы комбинируете их по мере необходимости, чтобы создать именно то поведение, которое вам нужно.
Задание
ЛегкоДавайте создадим систему заказа пиццы, используя паттерн Декоратор. Вы создадите базовую пиццу, а затем будете динамически добавлять к ней топпинги, где каждый топпинг оборачивает предыдущий слой и дополняет как описание, так и цену.
Вы организуете свой код в трех файлах:
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.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'Необязательные параметрыИменованные аргументыПерегрузка методов