使用列表而不是装饰器模式?

时间:2017-04-22 22:50:49

标签: java list oop design-patterns decorator

一个装饰模式用例来自" Head First:Design Patterns"书让我有这个问题。我会尝试写下来:

  

这是一个咖啡店系统,里面有一些咖啡和很多调味品   你可以加入它们(需要额外付费),你需要能够订购   和顾客所需的任何调味品一起喝咖啡,和   避免完全混乱(例如布尔人要跟踪   调味品)使用装饰图案。我们有一个抽象的饮料   类,每种类型的咖啡作为混凝土组件和每种调味品   作为包装Beverage的具体装饰器,如下所示:

     

Beverage Class Diagram

     

因此我们有以下流程返回咖啡费用:

     

Coffee Cost Delegation

我的问题是:为什么不用列表而不是装饰器来实现它?我们可以在每个饮料中有一份调味品清单,并通过迭代列表来计算成本。要订购咖啡,我们只需要实例化一次并添加所需的调味品,避免声明如下:

// Using second image example
Beverage beverage = new DarkRoast(beverage);
beverage = new Mocha(beverage);
beverage = new Whip(beverage);

除此之外,我们还可以更灵活地进行操作,例如,一旦我们不让包装咖啡的装饰品给咖啡打折,不包括它的调味品。这是一个长期研究过的问题,我知道我错过了一些东西,或者说我错了,所以如果你对此有任何想法,我很想知道并进一步讨论。

6 个答案:

答案 0 :(得分:3)

Decorator模式是关于在运行时装饰(增强)具有附加功能的对象。

假设您已经有一个类,我们可以将其称为类A,它实现了一个接口IA。现在,如果需要添加我们希望它有一个方法的附加功能someAlignFeatureToA()A中没有。现在您可以选择扩展类A。另一种方法是Composition,应优先于Inheritance。您可以将类A的对象包装在另一个类B中,其中InterfaceA相同,即IA。对于客户端代码,这种方式很容易接受B类的对象,因为它具有与A相同的接口。(假设代码写得很好,取决于抽象(接口IA)而不是关于类A)这样的具体类。

这样,继承链不会太长,您可以在运行时轻松添加其他功能。

现在回答您的问题:在您提出的问题示例中,是的,列表更适合,但我觉得作者的意图是通过一个简单的示例来解释装饰器模式的用法。 假设咖啡由牛奶,咖啡混合物和糖组成。咖啡混合物进一步由较小的组分组成。这里的解决方案与Composite模式类似。

装饰器模式是基于行为增强的设计模式。 Java IO流使用这个,其中行为(方法实现)由装饰类增强(一个流用另一个流包装以增强前一个流)

答案 1 :(得分:1)

您应该将数据与行为区分开来。

装饰器是一个间接层,处理一个非常具体的问题。数据(在类型,合同,协议等方面)保持不变,但您在实施方面可以获得更大的灵活性。使用装饰器,您可以重用现有功能并开始添加更改 - 结合适配器,您可以基本上慢慢地开始从pone API迁移到另一个并保持兼容。

列表应仅负责以特定方式提供对包含数据的访问。对数据执行任务/处理列表中包含的数据应该由具有不同职责的函数/对象/类来完成。

答案 2 :(得分:1)

您所说的是一种计算饮料总成本的方法。更一般地说,你提出了另一种合并方式。执行Decorator Pattern假定的每个派生对象的主要行为,将它们添加到列表中。而且它也不是一个OO方法。

Decorator Pattern的原始意图除此之外还有更广泛的视角。 动态地向对象添加扩展功能 。这意味着我有一些对象O1&我希望它将它转换为另一个具有扩展功能的对象O2和但是那两个是可以互换的。

因此,我们如何针对 Object Lifecycle 管理 对象演变 。希望你明白了。 :))

答案 3 :(得分:1)

免责声明:我使用的术语和代码段摘自this article

您已经注意到,有两种可能的设计来“装饰”一个对象。两种方法都可以实现相同的功能,主要区别是使用环境

垂直装饰器

Numbers numbers = new Sorted(
  new Unique(
    new Odds(
      new Positive(
        new ArrayNumbers(
          new Integer[] {
            -1, 78, 4, -34, 98, 4,
          }
        )
      )
    )
  )
);

这是实现装饰器的最常见方法。我几乎专门以这种方式实现我的装饰器,因为我发现它是“自然”和简单的方式。当装饰器的数量不是太多(多少取决于程序员的解释)时,它非常适合。 当某些对象需要动态扩展时,它也很适合:当实例化时不知道需要哪些装饰器时。

水平装饰器

Numbers numbers = new Modified(
  new ArrayNumbers(
    new Integer[] {
      -1, 78, 4, -34, 98, 4,
    }
  ),
  new Diff[] {
    new Positive(),
    new Odds(),
    new Unique(),
    new Sorted(),
  }
);

在极少数情况下(当装饰器的数量开始增长时),我做出设计选择,以将装饰器“扁平化”成类似列表的结构。这具有简化代码读取的优点,但具有在运行时失去灵活性的缺点:在对象创建时需要知道要应用哪个装饰器(在不变性的情况下尤其如此,我尝试着这种方法) ,因此根据应用程序的不同,这可能是不可行的。

最后,您只需要在灵活性和可读性之间做出设计决定即可。

答案 4 :(得分:0)

我知道我来不及了,但是我会尽力解释这一点。

我认为Head First本书中的示例很好地解释了这种模式,尽管许多人在找到简单的替代方案(例如该示例的集合)时感到困惑。

我认为更好的例子是送餐应用程序(WhySoHungry)。他们列出了

之类的多个优惠
  1. 生日特惠
  2. 周日晚上报价
  3. 提供10年的应用发布优惠。
  4. 客户的100订单报价

列表继续显示。

如果某人有资格获得多个要约,并且WhySoHungry希望他们同时应用所有要约,则计算部分将变得很困难。这是应用装饰图案的最佳位置。

答案 5 :(得分:0)

试图帮助OP理解装饰器正在扩展行为,这一点在第一本书的示例中并不明显。

通过freedev引用评论:
这与您必须扣除或增加的折扣或税款清单无关。它与您使用装饰器模式处理的行为有关。而且,相信我,与继承相比,使用列表处理这些行为更加困难。

在20个国家(= 20个租户)开展业务的多租户真实电子商务应用程序中的共享示例。

用例:构建一个可由UI呈现的购物车(或购物篮)对象。

要求:

  1. 购物车视图需要同时显示在购物车页面和订单查看页面上。
  2. 购物车中可能包含一些正在升级/降级的项目(您正在升级现有的宽带计划)。
    • 因此,我们可能会显示一些其他信息。 (您的升级后的计划将在接下来的x小时内激活。您将被罚款2 $。等等。)
    • 我们还可能会显示将要升级/降级的旧计划。 (向用户显示为购物车商品,但显示为灰色)。
    • 以上再次取决于租户。
  3. 如果承租人允许库存限制,则考虑购物车中每个项目的库存状态。但是,当我们在订单查看页面上看到购物车对象时,这不是必需的,因为我们已经在结帐时保留了购物车。
  4. 同一购物车对象的内部也包含购物车摘要(组成),但是在订单查看页面上我们不需要相同的购物车摘要。
  5. 我们还使用相同的购物车对象构造了另一个视图-当用户将鼠标悬停在UI标题中的购物篮图标上时显示的轻量级购物车视图。

现在,这几乎与描述的咖啡问题OP类似。口味太多(租户特定+页面特定行为)。
我们能做什么?根据需求,我们可以使用装饰器模式来装饰购物车。

可能的装饰器:

  1. CartStatusDecorator
  2. CartAdditionalInformationDecorator
  3. CartExistingItemsDecorator
  4. CartSummaryDecorator

继续如下:

  1. 构造一个基本的购物车对象,该对象具有所有必需的最低限度的行为。例如,此基本购物车应具有所有购物车项目及其价格,图像和数量。
  2. 根据承租人确定我们是否需要这些物品的状态。如果是,请在CartStatusDecorator中循环。
  3. 确定是否需要显示“其他信息”。如果是,请在CartAdditionalInformationDecorator中循环。
  4. 确定是否需要显示较早的计划。如果是,请在CartExistingItemsDecorator中循环。
  5. 确定是否要显示摘要。致电CartSummaryDecorator。

顶层可以根据租户和页面决定所有装饰者都需要参与其中。想象一下,通过一系列行为来做到这一点。现在很明显,列表将不是一个好主意。