Рассмотрим этот вариант использования. Пишем заявку для ресторана. В этом ресторане вы можете заказать как отдельное блюдо, так и блюдо с дополнительными опциями. Эти дополнительные опции на данный момент включают либо напиток, либо гарнир. Владелец этого ресторана экспериментирует со своей бизнес-моделью и попросил нас быть гибкими в отношении этих вариантов. Могут быть добавлены новые или удалены существующие варианты в зависимости от того, как люди реагируют на это. Но в одном владелец уверен: каждая транзакция требует покупки основного блюда (в этом месте нельзя есть картофель фри 😒).

Здесь мы знаем, что есть определенные компоненты, которые являются стандартными, и в зависимости от ситуации нам нужно динамически украсить эти стандартные компоненты. Шаблон декоратора хорошо подходит для этого типа вариантов использования.

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

public interface IFood
{
    string GetDescription();
    double GetTotalCost();   
}

Интерфейс iFood требует двух вещей. Метод Get description возвращает описание заказанных товаров. Метод Get total cost возвращает общую стоимость заказа. Ниже приведена реализация этого интерфейса по умолчанию.

public class FoodImplemetation:IFood
{
   private string Description = "Entree";
   private double Cost = 4;
   public string GetDescription()
   {
      return Description;
   }
   
   public double GetTotalCost()
   {
      return Cost;
   }
}

На этом этапе мы позаботились о фиксированных предположениях для нашего приложения. Для динамических частей нам нужно написать абстрактный класс декоратора, который может использоваться конкретными декораторами, которые мы хотим реализовать. Ниже показано, как будет выглядеть наш абстрактный декоратор.

public abstract class Decorator
{
    private IFood Food;
    public Decorator(IFood food)
    {
        Food = food;
    }
    public string GetDescription()
    {
       return Food.GetDescription();
    }
    public double GetTotalCost()
    {
        return Food.GetTotalCost();
    }
}

В приведенном выше классе конструктор принимает любое конкретное украшение, которое мы хотим реализовать. Ниже приведены две реализации, которые мы решили реализовать на данный момент. Эти два класса добавят дополнительные сведения к описанию и обновят стоимость заказа в зависимости от дополнительных товаров, которые покупатели выбрали для заказа. Во-первых, добавление напитка, которое показано ниже.

public class FoodWithDrink : Decorator
{
    private string description = " and a drink";
    private double cost = 1.5;
    
    public FoodWithDrink(IFood food):base(food)
    {
    }
    public string GetDescription()
    {
        return base.GetDescription() + description;
    }
    public double GetTotalCost()
    {
        return base.GetTotalCost() + cost;
    }
}

Во-вторых, добавление стороны. Который, как показано ниже, будет очень похож на класс выше.

public class FoodWithSide : Decorator
{
    private string description = " and a side";
    private double cost = 2.5;
    
    public FoodWithSide(IFood food):base(food)
    {
    }
    public string GetDescription()
    {
        return base.GetDescription() + description;
    }
    public double GetTotalCost()
    {
        return base.GetTotalCost() + cost;
    }
}

Полезность шаблона заключается в том, что мы можем добавить любое количество этих декораторов в зависимости от динамических потребностей наших требований. Наконец, давайте свяжем все эти компоненты вместе в нашем классе Runner.

using System;
public class Runner
{
    public static void Main(string[] args)
    {
        FoodImplemetation Food = new FoodImplemetation();
        Console.WriteLine(String.Format("Total cost for {0} is {1}",Food.GetDescription(),Food.GetTotalCost()));
        FoodWithSide FoodWithSide = new FoodWithSide(Food);
        Console.WriteLine(String.Format("Total cost for {0} is   {1}",FoodWithSide.GetDescription(),FoodWithSide.GetTotalCost()));
        FoodWithDrink FoodWithDrink = new FoodWithDrink(Food); 
        Console.WriteLine(String.Format("Total cost for {0} is {1}",FoodWithDrink.GetDescription(),FoodWithDrink.GetTotalCost()));
    }
}

Давайте внимательно посмотрим, что происходит в классе Runner. Во-первых, мы создали экземпляр реализации нашего интерфейса по умолчанию. В нем нет декораторов. Таким образом, он должен печатать описание и стоимость ничего, кроме нашего блюда. Во-вторых, мы создали экземпляр класса FoodWithSide, и этому классу мы передаем нашу реализацию по умолчанию. Класс FoodWithSide украсит объект нашей реализации по умолчанию дополнительными деталями, чтобы отразить, что сторона была добавлена ​​​​к заказу. Класс FoodWithDrink сделает то же самое. Ниже приведен вывод, когда я запускаю программу.

Полный код можно найти здесь.

Надеюсь, вам понравилась эта статья о шаблонах проектирования! Это моя первая статья на медиуме🥳. Если у вас есть какие-либо отзывы, пожалуйста, оставьте мне комментарий.😄