Когда использовать шаблон декоратора?

Я просматриваю свои шаблоны проектирования, и один образец, который мне еще предстоит серьезно использовать в моем кодировании, - это шаблон Decorator.

Я понимаю шаблон, но то, что я хотел бы знать, - это некоторые хорошие конкретные примеры раз в реальном мире, что образец декоратора - лучшее/оптимальное/элегантное решение. Конкретные ситуации, когда потребность в шаблоне декоратора действительно удобна.

Спасибо.

Ответ 1

Шаблон декоратора часто используется для потоков: вы можете обтекать поток потоком, чтобы получить добавленную функциональность. Я видел это с картой .Net - насколько я знаю, это происходит в другом месте. Мой любимый использует GZipStream вокруг FileStream для дополнительного сжатия.

Ответ 2

Шаблон Decorator используется для добавления дополнительных функций к определенному объекту, а не к классу объектов. Легко добавить функциональность ко всему классу объектов путем подклассификации объекта, но невозможно таким образом расширить один объект. С помощью шаблона Decorator вы можете добавить функциональность к одному объекту и оставить другие без изменений.

В Java классическим примером шаблона декоратора является реализация потоков ввода-вывода Java.

FileReader       frdr = new FileReader(filename);
LineNumberReader lrdr = new LineNumberReader(frdr);

Предыдущий код создает читатель - lrdr - который читает из файла и отслеживает номера строк. Строка 1 создает устройство чтения файлов (frdr), а строка 2 добавляет отслеживание номера строки.

На самом деле, я настоятельно рекомендую вам посмотреть исходный код Java для классов ввода/вывода Java.

Ответ 3

Недавно я использовал шаблон декоратора в веб-службе, которая использует следующий интерфейс CommandProcessor:

public Command receive(Request request);
public Response execute(Command command);
public void respond(Response response);

В принципе, CommandProcessor получает запрос и создает надлежащую команду, выполняет команду и создает соответствующий ответ и отправляет ответ. Когда я захотел добавить время и зарегистрировать его, я создал TimerDecorator, который использовал существующий CommandProcessor в качестве своего компонента. TimerDecorator реализует интерфейс CommandProcessor, но просто добавляет время, а затем вызывает его цель, которая является настоящим CommandProcessor. Что-то вроде этого:

public class TimerDecorator implements CommandProcessor {
   private CommandProcessor target;
   private Timer timer;

   public TimerDecorator(CommandProcessor processor) {
      this.target = processor;
      this.timer = new Timer();
   }

   public Command receive(Request request) {
      this.timer.start();
      return this.target.receive(request);
   }

   public Response execute(Command command) {
      return this.target.execute(command);
   }

   public void respond(Response response) {
      this.target.response(response);
      this.timer.stop();
      // log timer
   }

}

Таким образом, настоящий CommandProcessor обернут внутри TimerDecorator, и я могу обрабатывать TimerDecorator так же, как и целевой CommandProcessor, но теперь добавлена ​​логика синхронизации.

Ответ 4

Decorator шаблон динамически изменяет функциональность объекта во время выполнения без ущерба для существующей функциональности объектов.

Ключевые варианты использования:

  • Динамически добавлять дополнительные функции/обязанности
  • Устранение функциональности/ответственности динамически
  • Избегайте слишком большого количества субклассификации для добавления дополнительных обязанностей.

Недостатки:

  • Чрезмерное использование принципа Open Closed (Открыть для расширения и закрыто для модификации). Используйте эту функцию экономно, где код менее всего изменился.
  • Слишком много небольших классов и добавит накладные расходы на обслуживание.

Пример реального мира: Вычислить цену напитка, который может содержать несколько ароматов.

abstract class Beverage {
    protected String name;
    protected int price;
    public Beverage(){

    }
    public  Beverage(String name){
        this.name = name;
    }
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    protected void setPrice(int price){
        this.price = price;
    }
    protected int getPrice(){
        return price;
    }
    protected abstract void decorateBeverage();

}
class Tea extends Beverage{
    public Tea(String name){
        super(name);
        setPrice(10);
    }
    public void decorateBeverage(){
        System.out.println("Cost of:"+ name +":"+ price);
        // You can add some more functionality
    }
}
class Coffee extends Beverage{
    public Coffee(String name){
        super(name);
        setPrice(15);
    }
    public void decorateBeverage(){
        System.out.println("Cost of:"+ name +":"+ price);
        // You can add some more functionality
    }   
}
abstract class BeverageDecorator extends Beverage {
    protected Beverage beverage;
    public BeverageDecorator(Beverage beverage){    
        this.beverage = beverage;   
        setName(beverage.getName()+"+"+getDecoratedName());
        setPrice(beverage.getPrice()+getIncrementPrice());
    }
    public void decorateBeverage(){
        beverage.decorateBeverage();
        System.out.println("Cost of:"+getName()+":"+getPrice());
    }   
    public abstract int getIncrementPrice();
    public abstract String getDecoratedName();
}
class SugarDecorator extends BeverageDecorator{
    public SugarDecorator(Beverage beverage){
        super(beverage);
    }
    public void decorateBeverage(){
        super.decorateBeverage();
        decorateSugar();        
    }
    public void decorateSugar(){
        System.out.println("Added Sugar to:"+beverage.getName());
    }
    public int getIncrementPrice(){
        return 5;
    }
    public String getDecoratedName(){
        return "Sugar";
    }
}
class LemonDecorator extends BeverageDecorator{
    public LemonDecorator(Beverage beverage){
        super(beverage);
    }
    public void decorateBeverage(){
        super.decorateBeverage();
        decorateLemon();    
    }
    public void decorateLemon(){
        System.out.println("Added Lemon to:"+beverage.getName());       
    }
    public int getIncrementPrice(){
        return 3;
    }
    public String getDecoratedName(){
        return "Lemon";
    }
}

public class VendingMachineDecorator {  
    public static void main(String args[]){
        Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
        beverage.decorateBeverage();
        beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
        beverage.decorateBeverage();
    }
}

выход:

Cost of:Assam Tea:10
Cost of:Assam Tea+Lemon:13
Added Lemon to:Assam Tea
Cost of:Assam Tea+Lemon+Sugar:18
Added Sugar to:Assam Tea+Lemon
Cost of:Cappuccino:15
Cost of:Cappuccino+Lemon:18
Added Lemon to:Cappuccino
Cost of:Cappuccino+Lemon+Sugar:23
Added Sugar to:Cappuccino+Lemon

В этом примере вычисляется стоимость напитка в торговом автомате после добавления многих вкусов в напиток.

В приведенном выше примере:

Стоимость чая = 10, лимон = 3 и сахар = 5. Если вы делаете сахар + лимон + чай, он стоит 18.

Стоимость кофе = 15, лимон = 3 и сахар = 5. Если вы делаете сахар + лимон + кофе, он стоит 23

Используя тот же Decorator для обоих напитков (Tea and Coffee), количество подклассов было уменьшено. В отсутствие шаблона Decorator у вас должны быть разные подклассы для разных комбинаций.

Комбинации будут такими:

SugarLemonTea
SugarTea
LemonTea

SugarLemonCapaccuino
SugarCapaccuino
LemonCapaccuino

и т.д..

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

Связанный вопрос SE:

Рисунок декоратора для IO

Полезные ссылки:

design-patterns-decorator по dzone

decorator с помощью sourcemaking

oodesign статья

Ответ 5

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

IOrderGateway
{
    void PlaceOrder(Order order);
{

Основная реализация: AmazonAffiliateOrderGateway

Возможные декораторы могут быть:

  • IncrementPerformanceCounterOrderGateway
  • QueueOrderForLaterOnTimeoutOrderGateway
  • EmailOnExceptionOrderGateway
  • InterceptTestOrderAndLogOrderGateway

Более подробный пример отсюда иллюстрирует декоратор, который сохраняет контакты для заказов, которые были созданы с использованием подарочной карты при выполнении заказа:

class OrderWithGiftCardGateway extends OrderGatewayDecorator
{
    ...

    public function createOrder(CreateOrderRequest $order)
    {
        if ($this->containsGiftCard($order))
        {
            $this->addContactToFolder($order);
        }

        return parent::createOrder($order);
    }
}

Ответ 7

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

Ответ 8

Рисунок декоратора используется языком С#. Он используется, чтобы украсить класс ввода/вывода потока С#. Украшенные версии - это BufferedStream, FileStrem, MemoryStrem, NetworkStream и CryptoStream.

Эти подклассы наследуются от класса Stream и также содержат экземпляр класса Stream.

Подробнее здесь