шаблон декоратора действительно необходим в этом примере?

Вот постановка задачи

Ресторан имеет 4 базы пиццы:

Пицца из цельнозерновой муки
Пицца на дровах
Пицца с сырной начинкой
Пицца на тонком тесте

Есть 8 начинок:

Помидор, лук, сыр, пеппорони, стручковый перец, чеснок, панир, картофель,

Рассчитайте цену пиццы, учитывая основу пиццы и 0 или более начинок с ней (при условии, что для каждой основы и начинки настроена некоторая цена).

Мое псевдокодовое решение для этого было:

    public class Pizza {
        public int getPrice(base, String... toppings){
            PizzaBaseMap.get(base) + toppingsMap.sum(t -> toppingsMap.get(t))
        }



    Hashmap<String, int> PizzaBaseMap= {
        whole_wheat : 1
        wood_fire : 2
        cheese_filled : 2
        thin_crust : 4
    }

    Hashmap<String, int> toppingsMap = {
        tomato : 1
        onion : 2
        cheese : 4
        pepporoni : 5
        capsicum : 2
        garlic : 2
        paneer : 4
        potato : 4
    }

    //client Code 

    int Price = new Pizza().getPrice("whole_wheat", ["tomato", "cheese"])

Мне действительно нужно использовать декоратор, как предлагает в своем примере книга шаблонов дизайна Headfirst? Решение с шаблоном декоратора выглядит примерно так:

    public interface iPizza
        {
             double cost();
        }

    //Decorator
    public interface iToppingDecorator:iPizza
    {
    }

    //Pizza types
    class WholeWheatPizza:iPizza
        {
            public double cost()
            {

            }
        }

    class WoodFire : iPizza
        {
            public double cost()
            {

            }
        }

     class CheeseFilled : iPizza
        {
            public double cost()
            {

            }
        }

      class Thincrust : iPizza
        {
            public double cost()
            {

            }
        }

    //Toppings inheriting Decorator Interface

    class CheeseTopping:iToppingDecorator
        {
             iPizza pizza;
             public CheeseTopping(iPizza pizzatype)
            {
                this.pizza = pizzatype;
            }

            public double cost()
            {
                return <price> + pizza.cost(); 
            }
        }

     class TomatoTopping:iToppingDecorator
        {
            iPizza pizza;
            public TomatoTopping(iPizza pizzatype)
            {
                this.pizza = pizzatype;
            }

            public double cost()
            {
                return <price> + pizza.cost();
            }
        }

     class OnionTopping:iToppingDecorator
        {
            iPizza pizza;
            public OnionTopping(iPizza pizzatype)
            {
                this.pizza = pizzatype;
            }

            public double cost()
            {
                return <price> + pizza.cost();
            }
        }

     class PepporoniTopping:iToppingDecorator
        {
            iPizza pizza;
            public PepporoniTopping(iPizza pizzatype)
            {
                this.pizza = pizzatype;
            }

            public double cost()
            {
                return <price> + pizza.cost();
            }
        }

     class CapsicumTopping:iToppingDecorator
        {
            iPizza pizza;
            public CapsicumTopping(iPizza pizzatype)
            {
                this.pizza = pizzatype;
            }

            public double cost()
            {
                return <price> + pizza.cost();
            }
        }

     class PaneerTopping:iToppingDecorator
        {
            iPizza pizza;
            public PaneerTopping(iPizza pizzatype)
            {
                this.pizza = pizzatype;
            }

            public double cost()
            {
                return <price> + pizza.cost();
            }
        }

     class GarlicTopping:iToppingDecorator
        {
            iPizza pizza;
            public GarlicTopping(iPizza pizzatype)
            {
                this.pizza = pizzatype;
            }

            public double cost()
            {
                return <price> + pizza.cost();
            }
        }

     class PotatoTopping:iToppingDecorator
        {
            iPizza pizza;
            public PotatoTopping(iPizza pizzatype)
            {
                this.pizza = pizzatype;
            }

            public double cost()
            {
                return <price> + pizza.cost();
            }
        }

    //client
    static void Main()
            {
                iPizza pizza1 = new WholeWheatPizza();
                pizza1 = new CheeseTopping(pizza1);

                Console.WriteLine("Pizza 1 cost: "+pizza1.cost()+"INR");

                iPizza pizza2 = new WoodFire();
                pizza2 = new CheeseTopping(pizza2);
                pizza2 = new TomatoTopping(pizza2);
                Console.WriteLine("Pizza 2 cost: " + pizza2.cost() + "INR");

                Console.ReadLine();

            }

Я просто чувствую, что это полный излишек, мой код так же расширяем, как и решение с шаблоном декоратора. Есть предположения?


person noob Mama    schedule 03.09.2015    source источник


Ответы (1)


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

В этих случаях есть две сильные стороны шаблона декоратора:

  1. Вы можете добавлять и удалять декораторы из своей кодовой базы независимо, потому что поведение каждого из них полностью содержится в его классе.
  2. Способ декорирования объекта можно настроить для каждого декоратора.

Например, предположим, что у нас есть специальное предложение, в котором любая пицца с анчоусами продается со скидкой 20 % (не ешьте анчоусы!). С хэш-картой это довольно громоздко:

foreach (var topping in toppings)
    if (topping is Anchovies)
        price := price * 0.8
    else
        price := price - toppingCosts[topping]

С шаблоном декоратора мы можем просто добавить новый класс:

class AnchoviesToppingDecorator : IPizzaToppingDecorator
{
    private IPizza pizza;

    public AnchoviesTopping(IPizza pizza)
    {
        this.pizza = pizza;
    }

    public double cost()
    {
        return this.pizza.Cost() * 0.8f;
    }
}

Конечно, тогда порядок применения декоратора становится важным. При необходимости это можно реализовать с помощью системы типов.


Вы можете упорядочить декораторы, используя систему типов. Предположим, что все декораторы топпинга должны предшествовать декораторам скидок. В нашей системе декораторы принимают своего предшественника через свой конструктор. Изменяя тип конструктора, мы можем ограничить декораторы, на которых мы можем основываться.

interface IPizzaToppingDecorator : IPizzaDecorator
{

}

interface IPizzaDiscountDecorator : IPizzaDecorator
{

}

public class HalfPriceDecorator : IPizzaDiscountDecorator 
{
    private IPizzaToppingDecorator pizzaToppingDecorator;

    public HalfPriceDecorator(IPizzaToppingDecorator pizzaToppingDecorator)
    {
        this.pizzaToppingDecorator = pizzaToppingDecorator;
    }

    public double Cost()
    {
        pizzaToppingDecorator.Cost() * 0.5;
    }
}

Обратите внимание, что конструктор принимает IPizzaToppingDecorator, а не IPizzaDecorator. Это гарантирует, что HalfPriceDecorator может применяться только после IPizzaToppingDecorator.

Все остальное вызовет ошибку времени компиляции; и нам нравятся ошибки времени компиляции, потому что только мы их видим!


В этом случае я думаю, что пример Head First можно улучшить с помощью плавного интерфейса.

Разве не было бы здорово написать это?

new WholeWheatPizza().WithTomatoTopping().WithCheeseTopping().WithPepporoniTopping();

Этого можно добиться с помощью методов расширения:

public static IPizza WithCheeseTopping(this IPizza pizza)
{
    return new CheeseToppingDecorator(pizza)
}
person sdgfsdh    schedule 03.09.2015
comment
спасибо, мне очень нравится расширение, которое вы предложили. Но даже если количество начинок увеличилось в n раз, разве это не просто добавление новых записей в вашу хэш-карту? Далее мы могли бы сделать такую ​​же простую структуру для каждого продукта (скажем, кофе вместо пиццы). Даже если конечный пользователь настроил начинки, можем ли мы не считать конфигурацию в хэш-карту и использовать ее вместо этого? Я согласен с тем, что вы можете добавлять и удалять декораторы из своей кодовой базы независимо, но это действительно имеет смысл только в том случае, если я создавал библиотеку, в которой я не ожидаю, что люди изменят мои базовые классы. - person noob Mama; 03.09.2015
comment
Да, это хороший момент. Я добавил к ответу второе преимущество, которое хэш-карта не могла покрыть (по крайней мере, элегантно!) - person sdgfsdh; 03.09.2015
comment
хм... интересный момент, это именно то, что я чувствовал, когда читал пример в шаблонах проектирования. Я не уверен, как бы вы вписались в систему типов с шаблоном декоратора, хотя... есть ссылки на примеры? - person noob Mama; 03.09.2015
comment
Добавил примечание к ответу - person sdgfsdh; 03.09.2015