【问题标题】:When to Use the Decorator Pattern?何时使用装饰器模式?
【发布时间】:2010-12-05 16:54:57
【问题描述】:

我正在回顾我的设计模式,而我在编码中尚未认真使用的一种模式是装饰器模式。

我了解该模式,但我想知道的是现实世界中装饰器模式是最佳/最佳/优雅解决方案的一些很好的具体示例。需要装饰器模式的特定情况非常方便。

谢谢。

【问题讨论】:

标签: design-patterns decorator


【解决方案1】:

Decorator 模式用于向现有的object(即在运行时已经实例化的类)添加额外的功能,而不是对象的class 和/或子类。通过子类化对象的类很容易为整个对象类添加功能,但不可能以这种方式扩展单个对象。使用装饰器模式,您可以向单个对象添加功能,而其他类似的对象保持不变。

在 Java 中,装饰器模式的一个经典示例是 Java I/O Streams 实现。

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

前面的代码创建了一个阅读器——lrdr——从文件中读取并跟踪行号。第 1 行创建文件阅读器 (frdr),第 2 行添加行号跟踪。

实际上,我强烈建议您查看 Java I/O 类的 Java 源代码。

【讨论】:

    【解决方案2】:

    装饰器模式在流中被大量使用:您可以用流包装流以获得附加功能。我已经在 .Net 框架中看到了这一点——据我所知,这发生在其他地方。我最喜欢的是在 FileStream 周围使用 GZipStream,以增加压缩。

    【讨论】:

    • 类似地,Java 流只是一个巨大的装饰器集合,装饰器装饰了装饰的东西。我的脸都融化了。
    • 我认为流比我的 Head First: Design Patterns book on my desk 中的咖啡示例更好。
    • 谁能解释一下为什么装饰器模式是这种情况的最佳解决方案?
    【解决方案3】:

    我最近在使用以下 CommandProcessor 接口的 Web 服务中使用了装饰器模式:

    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 模式在运行时动态更改对象的功能,而不会影响对象的现有功能。

      关键用例:

      1. 动态添加额外的功能/职责
      2. 动态删除功能/职责
      3. 避免过多的子类化以增加额外的职责。

      缺点:

      1. 过度使用 Open Closed 原则(Open for extension and Closed for modify)。在代码最不可能更改的地方谨慎使用此功能。
      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。

      Cost of Coffee =15, Lemon = 3 and Sugar = 5. 如果你做糖 + 柠檬 + 咖啡,它的成本是 23

      通过对两种饮料(茶和咖啡)使用相同的装饰器,减少了子类的数量。在没有装饰器模式的情况下,您应该为不同的组合设置不同的子类。

      组合将是这样的:

      SugarLemonTea
      SugarTea
      LemonTea
      
      SugarLemonCapaccuino
      SugarCapaccuino
      LemonCapaccuino
      

      等等。

      通过对两种饮料使用相同的装饰器,减少了子类的数量。这可能是由于此模式中使用的 composition 而不是 inheritance 概念。

      相关的 SE 问题:

      Decorator Pattern for IO

      有用的链接:

      design-patterns-decorator 来自 dzone

      decorator 来源制作

      oodesign文章

      【讨论】:

        【解决方案5】:

        装饰器简单但功能强大。它是实现关注点分离的关键,也是开放封闭原则的重要工具。举一个常见的下单产品示例:

        IOrderGateway
        {
            void PlaceOrder(Order order);
        {
        

        主要实现:AmazonAffiliateOrderGateway

        可能的装饰器可能是:

        • IncrementPerformanceCounterOrderGateway
        • QueueOrderForLaterOnTimeoutOrderGateway
        • EmailOnExceptionOrderGateway
        • InterceptTestOrderAndLogOrderGateway

        来自here 的更详细示例说明了一个装饰器,该装饰器为在完成订单时使用礼品卡创建的订单保存联系人:

        class OrderWithGiftCardGateway extends OrderGatewayDecorator
        {
            ...
        
            public function createOrder(CreateOrderRequest $order)
            {
                if ($this->containsGiftCard($order))
                {
                    $this->addContactToFolder($order);
                }
        
                return parent::createOrder($order);
            }
        }
        

        【讨论】:

          【解决方案6】:
          1. 动态、透明地向各个对象添加职责。
          2. 可以撤销的职责。
          3. 当通过子类化扩展不切实际时。有时大量独立的扩展是可能的,并且会产生大量的子类来支持每种组合。

          【讨论】:

            【解决方案7】:

            C# 语言本身使用装饰器模式。用于装饰 C# 的 Stream I/O 类。修饰的版本是 BufferedStream、FileStrem、MemoryStrem、NetworkStream 和 CryptoStream 类。

            这些子类继承自 Stream 类,并且还包含 Stream 类的一个实例。

            阅读更多here

            【讨论】:

              【解决方案8】:

              当您想向一个类/对象添加多个功能并希望在需要时灵活地添加它们,Decorator 就派上用场了。您可以扩展基类并添加您想要的更改,但这样您会想出许多可能会让您大吃一惊的子类。但是使用装饰器,您可以进行所需的更改,但仍然有一个简单、易于理解的流程。 您的设计很容易打开以进行扩展,但可以通过非常简单的逻辑进行修改。 最好的例子可能是用 Java 和 C# 实现的 Stream 对象。 例如,您有一个文件流,并且在一个用例中,您想对其进行加密,然后对其进行压缩,然后对其进行记录,最后对其进行一些花哨的操作。然后在另一个班级中,您决定做其他事情您想要转换它,然后加密它,然后获取时间,bluh,bluh,bluh。再次,您在其他课程中有另一个流程。 如果要使用继承,则必须创建至少 3 个子类,如果需要任何其他要求,则必须添加更多子类,在这种情况下(流),您最终将获得数十个子类进行小的更改.

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

              在每个用例中,您必须记住需要什么类并尝试使用它。 但是假设您使用的是组合而不是继承,并且每个 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.
              }
              

              依此类推,您可以灵活地在运行时通过简单的流程和可理解的逻辑来做任何您想做的事情。

              【讨论】:

                【解决方案9】:

                GOF定义:

                动态地为对象附加额外的职责。装饰器为扩展功能提供了一种灵活的替代子类的方法。

                这种模式说,类必须对修改关闭但对扩展开放,可以添加新功能而不会干扰现有功能。当我们想向特定对象而不是整个类添加特殊功能时,这个概念非常有用。在这种模式中,我们使用对象组合的概念而不是继承。

                通用示例:

                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();
                    }
                }
                

                【讨论】:

                  【解决方案10】:

                  对我来说一个非常真实的例子:

                  我不得不更新一个在项目中大量使用的类,但该类在库中,源代码在地狱中丢失了。

                  我可以反编译整个库以创建它的另一个版本,也可以使用装饰器设计模式,我这样做了。这使我能够添加缺少的功能并简单地更新配置。

                  负责任地使用它是一种非常有用的模式。

                  这个特殊案例启发了我创建this video,我在其中解释了模式。

                  【讨论】:

                    猜你喜欢
                    • 2013-05-07
                    • 2020-02-29
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-08-25
                    • 1970-01-01
                    • 1970-01-01
                    • 2018-04-10
                    相关资源
                    最近更新 更多