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

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

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

Благодарю.

11 ответов

Решение

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

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

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

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

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

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

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

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

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

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

Недостатки:

  1. Чрезмерное использование принципа Open Closed ( Open для расширения и Closed для модификации). Используйте эту функцию экономно, когда код наименее вероятно изменился.
  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:

Декоратор Pattern для IO

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

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

декоратор путем создания источника

статья о дизайне

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

IOrderGateway
{
    void PlaceOrder(Order order);
{

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

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

  • IncrementPerformanceCounterOrderGateway
  • QueueOrderForLaterOnTimeoutOrderGateway
  • EmailOnExceptionOrderGateway
  • InterceptTestOrderAndLogOrderGateway

Посмотрите здесь для более подробного примера.

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

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

Еще немного информации: http://framework.zend.com/manual/en/zend.form.decorators.html

Определение ГОФ :

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

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

Общий пример:

      public abstract class Decorator<T> {
    private T t;

    public void setTheKernel(Supplier<? extends T> supplier) {
        this.t = supplier.get();
    }

    public T decorate() {
        return Objects.isNull(t) ? null : this.t;
    }

}

Реализация

      public interface Repository {
    void save();
}

public class RepositoryImpl implements Repository{
    @Override
    public void save() {
        System.out.println("saved successfully");
    }
}

public class EnhancedRepository<T> extends Decorator<T> {
    public void enhancedSave() {
        System.out.println("enhanced save activated");
    }
}

public class Main {
    public static void main(String[] args) {
        EnhancedRepository<Repository> enhanced = new EnhancedRepository<>();
        enhanced.setTheKernel(RepositoryImpl::new);
        enhanced.enhancedSave();
        enhanced.decorate().save();
    }
}

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

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

Узнайте больше здесь

Если вы хотите добавить несколько функций к классу / объекту и хотите иметь гибкость, чтобы добавлять их в любое время, вам пригодится Decorator. вы можете расширить базовый класс и добавить желаемые изменения, но таким образом вы получите множество подклассов, которые могут поразить вас. но с декоратором вы можете вносить желаемые изменения, но при этом иметь простой, понятный процесс. ваш дизайн легко открыть для расширения, но близок к модификации с действительно простой логикой. Лучшим примером могут быть объекты Stream, реализованные на Java и C#. например, у вас есть файловый поток, и в одном варианте использования вы хотите его зашифровать, затем заархивировать, затем записать в журнал и, наконец, сделать с ним что-нибудь интересное. затем в другом классе вы решаете сделать что-то еще. Вы хотите преобразовать это, затем зашифровать это, затем получить время, блух, блух, блух.снова у вас есть другой поток в других классах. если вы хотите использовать наследование, вам нужно создать как минимум 3 подкласса, и если требуется какое-либо другое требование, вам нужно добавить больше подклассов, которые в этом случае (Stream) вы получите десятки подклассов для небольших изменений.

class EncryptZipLogStream{}
class ConvertEncryptTimeStream{}
class MoreStreamProcess{}
class OtherMoreStreamProcess{}
...

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

class WhereIWannaUseUseCaseOne {
    EncryptStream(ZipStream(LogStream(FileStream("file name)))));
    // rest of the code to use the combines streams.
}

тогда вы придумали другой вариант использования:

class WhereIWannaUseUseCaseSecond {
    ConvertStream(TimeStream(LogStream(FileStream("file name)))));
    // rest of the code to use the combines streams.
}

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

Очень реальный пример для меня:

Мне пришлось обновить класс, который активно использовался в проекте, но этот класс находился в библиотеке, а исходный код был утерян в преисподней.

Я мог либо декомпилировать всю библиотеку, чтобы создать другую ее версию, либо использовать шаблон проектирования декоратора, что я и сделал. Это позволило мне добавить недостающие функции и просто обновить конфигурацию.

При ответственном использовании это очень полезный шаблон.

Этот конкретный случай вдохновил меня на создание этого видео, в котором я объясняю узор.

Другие вопросы по тегам