【问题标题】:Decorator Pattern Questions装饰器模式问题
【发布时间】:2016-10-05 06:45:45
【问题描述】:

最近我读到了关于装饰器设计模式的文章,但我遇到了一些我无法在网上找到的未解决问题。我不会展示代码,因为我不想让这个问题变得比实际更复杂。我简单举个例子:

赛百味店:

组件 --> 子三明治

ConcreteComponent --> 15cmSub, 30cmSub

装饰器 --> 成分

ConcreteDecorator --> 白奶酪、黄奶酪、果酱、鸡肉。

这正是赛百味商店的运作方式。选择你的核心三明治大小,然后添加你喜欢的所有配料。但我还有一些问题:

  1. 如果成分组合无效怎么办?例如,赛百味的政策规定,同一个潜艇中不能有两种奶酪。现在让我们假设浇头有 10000 种可能的组合,只有一种是无效的。这是否完全打破了装饰器模式?

  2. 如果两种成分相互依赖怎么办。例如,如果您订购生菜,那么您需要其他种类的蔬菜来制作“有效”的 Sub。

  3. 什么时候最好使用装饰器模式而不是带有 ArrayList 的 SubSandwich 类?我知道这里的成分不会添加行为,这使得 Subway 示例不准确,但我们假设它们确实如此。

  4. 为什么要扩展?为什么不使用接口?

【问题讨论】:

  • 最后一个问题没看懂?使用接口意味着扩展这些接口。

标签: java design-patterns decorator


【解决方案1】:

原始结构中的模式不能解决任何实际问题。您需要将它们与一般 SOLID 原则混合使用。对于示例问题,您可以在装饰器之上实现构建器模式。你会有一些 DSL 类型的代码,例如

Builder.aPizza().withCheeze(someCheeze).addTopping(someTopping).and(someOtherTopping).build();

使用这种模式/样式,您可以在调用 build() 方法时验证对象的中间状态,甚至在最后阶段。

【讨论】:

    【解决方案2】:
    1. 模式并不适用于所有用例。装饰器模式实际上对于构建多组件结构并没有多大用处,因为它将所有内容组合成 1 个对象,只有 1 个接口。这仅适用于新行为仅仅是“装饰”的情况。

      一个很好的例子是Collections.synchronizedList(List list),它通过将synchronized 包裹在所有方法周围来“装饰”所有方法。

      您可能仍然可以为您描述的用例使用装饰器,但每个装饰步骤都必须检查是否可以应用,如果不可以则抛出。

      老实说,我什至不知道为什么这类示例如此受欢迎。我还没有见过这样使用的装饰器模式。它不是为添加组件而设计的。

    2. 参见 1。这甚至可能是不可能的,因为 lettuce 装饰器无法强制之后有另一个装饰器。这显然不是对模式的良好使用。

    3. 在您更喜欢它或效果更好的情况下。就像三明治工厂一样。

    4. 因为这是装饰者的伎俩。它装饰了现有事物的行为。您不想添加新界面。一个有用的地方是当你是中间人时:

      例如库 A 生成 ListItem 对象,您想使用库 B 来显示它们。你不能改变 A 生产的东西,也不能改变 B 消费它们的方式。但是,当您不喜欢例如 A 如何实现 B 用来显示的 toString() 方法时,您可以简单地包装一个新的 toString() 方法。它仍然是ListItem,所以 B 不会注意到,就像 A 也不会注意到任何东西一样。

    【讨论】:

      【解决方案3】:

      装饰器模式的意图和您的应用程序的意图不匹配。装饰器不适合您的用例。

      来源:https://en.wiki2.org/wiki/Decorator_pattern

      装饰器模式可用于静态扩展(装饰)某个对象的功能,或者在某些情况下在运行时独立于同一类的其他实例,前提是在设计时完成了一些基础工作

      您错过了装饰器意图中“独立”的要点。如果你想建立关联或互斥,装饰器是没有用的。

      另一方面,Builder_pattern 适合您的要求,因为您可以设置强制和可选参数来构建对象。

      您可以解决这些 SE 问题:

      When would you use the Builder Pattern?

      Builder Pattern in Effective Java

      【讨论】:

      • 太棒了!我会更加关注定义。并感谢您提供替代解决方案。
      【解决方案4】:
      1. 如果成分组合无效怎么办?例如,赛百味的政策规定,同一个潜艇中不能有两种奶酪。

      那么装饰器模式可能不是您想要的。装饰器是关于在不改变界面的情况下组合行为。这与连接 属性 无关,它不能很好地工作,因为只有最外层的装饰器实例(通常)对用户来说是直接可用的。如果您需要以某种方式知道 SubWithProvolone 是否包装了 SubWithTurkey - 或任何其他特定类型的子 - 那么您做错了。

      Java I/O 类InputStreamOutputStreamReaderWriter 及其所有子类展示了我所知道的最佳示例。任何InputStream 都可以通过用BufferedInputStream 装饰来缓冲;任何InputStream 都可以用作Reader,方法是用InputStreamReader 装饰它; 等等..

      但是,如果您开始引入诸如“BufferedWriter 不能被另一个 BufferedWriter 装饰”(假设)之类的规则,那么它就会失效——没有好的方法来执行它,事实上,没有善意需要规则。如果您试图模拟一个真正需要关于如何组合组件的规则的情况,那么 Decorator 不是一个很好的选择。

      1. 如果两种成分相互依赖怎么办。例如,如果您订购生菜 THEN,则您需要其他种类的蔬菜来制作“有效”的 Sub。

      这与#1 本质上是相同的问题。您可能会拼凑一些东西来解决这个问题和前一个问题,但如果这些问题都出现了,那么 Decorator 就不适合这种情况。

      1. 什么时候最好使用装饰器模式而不是带有 ArrayList 成分的 SubSandwich 类?我知道这里的成分不会添加行为,这使得 Subway 示例不准确,但我们假设它们确实如此。

      您的问题的其他方面对于 SO 来说有点宽泛,但这一方面太宽泛了。我们回答有关编程细节的具体问题。

      1. 为什么要扩展?为什么不使用接口?

      你询问一个错误的二分法。 Java 区分接口和纯抽象类是一种肤浅的语言设计细节,主要用于支持 Java 对实现的单一继承的限制。如果您的问题被重定向到 C++,那么接口和纯抽象类之间没有太大的区别,而设计模式与这些设计模式同样相关。您可以围绕作为 Java 接口的基类型实现装饰器模式。

      话虽如此,我已经提到的 Java I/O 类确实使用类而不是接口作为基本类型。有优点也有缺点,但优点之一是它允许他们使用模板方法模式来提供更容易实现的具体实现类。然而,即使在那里,即使装饰器类型被声明为接口,也可以为此目的提供抽象基类。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-05-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-17
        • 1970-01-01
        • 2011-02-20
        相关资源
        最近更新 更多