Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по 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) идеален, когда вам нужно добавить объектам новые обязанности без создания огромного количества подклассов. Каждый декоратор фокусируется на одной задаче, и вы комбинируете их по мере необходимости, чтобы создать именно то поведение, которое вам нужно.

challenge icon

Задание

Легко

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

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

  • 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: Вывести итоговое описание и цену
    }
}
quiz iconПроверьте себя

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

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