Когда использовать шаблон декоратора? - PullRequest
55 голосов
/ 11 октября 2009

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

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

Спасибо.

Ответы [ 8 ]

67 голосов
/ 11 октября 2009

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

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

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

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

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

36 голосов
/ 11 октября 2009

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

28 голосов
/ 11 октября 2009

Я недавно использовал шаблон декоратора в веб-сервисе, который использует следующий интерфейс 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, но теперь добавлена ​​логика синхронизации.

8 голосов
/ 29 мая 2016

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

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

  1. Динамически добавлять дополнительные функции / обязанности
  2. Динамическое удаление функций / обязанностей
  3. Избегайте слишком большого подкласса , чтобы добавить дополнительные обязанности.

Недостатки:

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

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

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 у вас должны быть разные подклассы для разных комбинаций.

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

SugarLemonTea
SugarTea
LemonTea

SugarLemonCapaccuino
SugarCapaccuino
LemonCapaccuino

и т.д..

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

Вопрос по теме SE:

Шаблон декоратора для ввода / вывода

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

Дизайн-шаблоны-декоратор от dzone

декоратор с помощью sourcemaking

oodesign артикул

7 голосов
/ 11 октября 2009

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

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);
    }
}
3 голосов
/ 11 октября 2009

Zend Framework использует декоратор для элементов формы

Дополнительная информация: http://framework.zend.com/manual/en/zend.form.decorators.html

2 голосов
/ 29 января 2016
  1. для динамического и прозрачного добавления обязанностей к отдельным объектам.
  2. для обязанностей, которые могут быть сняты.
  3. когда расширение с помощью подклассов нецелесообразно. Иногда возможно большое количество независимых расширений, что приведет к взрыву подклассов для поддержки каждой комбинации.
0 голосов
/ 06 декабря 2018

Шаблон декоратора используется самим языком C #. Он используется для украшения класса потокового ввода-вывода C #. Декорированные версии классифицированы как BufferedStream, FileStrem, MemoryStrem, NetworkStream и CryptoStream.

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

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

...