【问题标题】:Why in the decorator pattern the abstract decorator "has a" and "is a" abstract component?为什么在装饰器模式中抽象装饰器“有一个”和“是一个”抽象组件?
【发布时间】:2015-12-02 13:52:53
【问题描述】:

我在许多与装饰器模式相关的UML类图中看到,抽象装饰器既包含一个抽象组件,又扩展了抽象类组件。

例如,在第二个示例中,来自以下链接的咖啡制作场景

https://en.wikipedia.org/wiki/Decorator_pattern#Examples

CoffeeDecorator 扩展了抽象组件Coffee 并包含Coffee 类型的字段。这是为什么?这只是一个案例,还是我们应该始终构建这样的“装饰器系统”,为什么?

我认为CoffeeDecorator 只需要包含它想要装饰的组件,因为CoffeeDecorator 实际上不是Coffee

【问题讨论】:

    标签: java decorator


    【解决方案1】:

    装饰器/包装器可以在任何可以使用包装对象的地方使用(“is a”)。这就是为什么它必须扩展/实现类/接口。

    装饰器/包装器还具有对它要装饰/包装的实例的引用(“有一个”)。

    正如您问题中的链接示例所示,您可以编写:

     Coffee coffee = new WithMilk(new SimpleCoffee());
    

    WithMilk 是一个装饰器。

    【讨论】:

      【解决方案2】:

      你的问题对我来说似乎有点循环......实现一个接口并拥有一个“装饰”的接口实例是装饰器模式的定义。

      所以你的问题有点像问“为什么装饰器模式使用装饰器模式?”

      如果你要包装的东西和你自己之间没有共同的接口,它只是一种有关系,或者,如果你正在调整你要包装的东西的接口到其他接口,可能是适配器模式。

      我认为您真正的问题是“为什么装饰器模式有用?是什么让实现与您包装的东西相同的接口有用?”

      装饰器模式支持同一行为的变化

      装饰器模式的最大价值在于,当您有一些系统必须处理几个细微不同的事情,但又不想知道复杂性,并且能够以相同的方式对待它们时。当然,这通常是我们从接口中得到的。但在某些情况下,同一接口的大量实现并不是最佳解决方案……在经典示例中,假设您尝试为星巴克提供的每种咖啡编写一个单独的类。

      您将从基础开始,但逐渐发现自己需要编写像VentiMochaChaiWhipWithCinnamonOnTop 这样的类。每次添加一种新的风味成分,你就需要编写几十个新类,每次删除一个,就会有几十个类被淘汰。

      另一方面,通过装饰器模式,您可以为每种饮料设置一个单独的类,并使大部分软件系统不必处理各种饮品种类;他们可以通过Drink 界面对每种饮料一视同仁(无论它有多少附加组件)。

      当结帐的人点击 Chai 按钮时,您只需创建一个 new Chai(),因为其他属性可以添加到任意饮料中,您只需将其包装为 new Venti(drink)new Mocha(drink)new Whip(drink)new WithCinnamon(drink) 等,因为客户请求附加组件并且收银员点击按钮。

      装饰器模式的局限性

      该模式有其局限性,因为与仅对每种调味品使用一组布尔值相比,对您所喝的确切饮料进行自省要困难一些。我很确定这对于在为星巴克设计收银机时跟踪饮料类型实际上没有用处,因为它不会很好地处理“哎呀,我的意思是黑莓味,而不是覆盆子味”.. .在装饰器模式中支持这一点需要通过几个嵌套的 Drink 装饰器进行自省,以找到需要删除的 Rasberry 装饰器。

      它也不太擅长处理具有复杂相互关系的事物……如果搅打配料需要知道它与什么基础饮料类型相关联,以了解它是否应该在价格上增加 0.25 美元,那么装饰器模式就不会很有用:装饰器模式适用于各种装饰不必知道它们包装的确切实现的情况。当我们可以使用它时,它有助于减少耦合。

      我真正觉得有用的东西

      我认为我们使用drink 示例来解释装饰器模式主要是因为它很容易谈论在需要创建的类数量方面潜在的指数级节省,而许多实际示例说明了装饰某些东西的用处并不'实际上并没有使用装饰器模式的那个属性。

      我能想到的在我们公司使用接近模式的东西的案例是我们想在流程中添加一些日志记录的案例。例如,通过装饰一个 Eclipse IProgressMonitor,我们可以检测每次显示的任务或子任务名称的变化,并记录流程的每个步骤花费的时间,这样我们就知道哪些步骤花费的时间最多。

      作为装饰器这样做使我们能够访问所有日志信息,而无需更改大部分现有代码(只是要监视的作业的启动),因为它都知道如何处理 IProgressMonitor已经,而且我们也不必知道我们封装的进度监视器在内部做什么:我们不必提供替代实现,只需将现有实现封装为我们想要的附加行为。

      【讨论】:

      • 我的问题是不是:“为什么装饰器模式有用”。我知道它为什么有用:将动态行为与对象相关联。我对这个事实感到困惑:为什么包装类应该与组件严格兼容?感谢您对模式限制的额外解释。
      【解决方案3】:

      装饰器和装饰类实现相同的接口。这允许装饰类为装饰类的每个方法(在接口中声明)添加额外的功能。这就是包装/装饰的意思。

      public class SimpleCoffee extends Coffee {
          @Override
          public double getCost() {
              return 1;
          }
          @Override
          public String getIngredients() {
              return "Coffee";
          }
      }
      
      public abstract class CoffeeDecorator extends Coffee {
          protected final Coffee decoratedCoffee;
      
          public CoffeeDecorator(Coffee c) {
              this.decoratedCoffee = c;
          }
      
          public double getCost() { // Implementing methods of the abstract class
              //you can add  extra functionality here.
              return decoratedCoffee.getCost();
          }
      
          public String getIngredients() {
              //you can add  extra functionality here.
              return decoratedCoffee.getIngredients();
          }
      }
      

      装饰器类可以被更大的装饰器装饰。

      public class BiggerDecorator extends Coffee {
          protected final Coffee decoratedCoffee;
      
          public CoffeeDecorator(Coffee c) {
              this.decoratedCoffee = c;
          }
      
          public double getCost() { // Implementing methods of the abstract class
      
              return decoratedCoffee.getCost();
          }
      
          public String getIngredients() {
      
              return decoratedCoffee.getIngredients();
          }
      }
      

      您现在可以执行以下操作。 Java IO 类在此结构中。

      Coffee coffeeDecorator=new CoffeeDecorator(new Coffee);
      Coffee biggerDecorator=new BiggerDecorator(coffeeDecorator);
      

      为了多态性,装饰器类必须实现该接口。

      【讨论】:

        【解决方案4】:

        答案在GOF一书中装饰器模式介绍中提到。

        这是报价:

        "装饰器符合它所装饰的组件的接口 以便它的存在对组件的客户端是透明的。 透明度允许您递归嵌套装饰器,从而允许 无限数量的附加责任”。

        所以,我认为客户端不必担心两个不同的接口,一个用于装饰器,另一个用于组件。另一个原因是 Puce 的回答提到的。

        【讨论】:

        • 这最好是作为评论而不是答案
        猜你喜欢
        • 2011-01-02
        • 2019-01-18
        • 1970-01-01
        • 2017-05-13
        • 2013-10-20
        • 2013-04-12
        • 1970-01-01
        • 1970-01-01
        • 2011-09-03
        相关资源
        最近更新 更多