装饰模式问题

Decorator Pattern Questions

最近我读到了有关装饰器设计模式的内容,但我仍然有一些无法在网上找到的悬而未决的问题。我不打算展示代码,因为我不想让这个问题比实际情况更复杂。我简单举个例子:

地铁店:

组件 --> SubSandwich

混凝土组件 --> 15cmSub, 30cmSub

装饰器 --> 成分

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

这就是 Subway 商店的运作方式。选择您的核心三明治大小,然后添加您喜欢的所有配料。但我还有一些疑问:

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

  2. 如果两种成分相互依赖怎么办。例如,如果您点了生菜,那么您需要一些其他种类的蔬菜来制作 "valid" Sub.

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

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

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

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

使用此 pattern/style 您可以验证对象的中间状态,甚至在调用 build() 方法时的最后阶段。

  1. 模式并非适用于所有用例。装饰器模式实际上对于构建多组件结构没有多大用处,因为它将所有内容组合到 1 个对象中,只有 1 个接口。这仅适用于新行为仅为 "decoration".

    的情况

    一个很好的例子是 Collections.synchronizedList(List list),其中 "decorates" 所有方法都通过在它们周围包裹 synchronized 块。

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

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

  2. 参见 1。这甚至可能是不可能的,因为生菜装饰器不能强制要求之后还有另一个装饰器。这显然不是一个很好的模式使用。

  3. 在您更喜欢它或效果更好的情况下。比如三明治工厂。

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

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

  1. What if there is an invalid combination of ingredients? For example, Subway policy says that there can't be two kinds of cheeses in the same sub.

那么装饰器模式可能不是您要找的。 Decorator 是关于在不更改界面的情况下编写 行为 。它与连接 属性 无关,并且它对此效果不佳,因为只有最外层的装饰器实例(通常)直接可供用户使用。如果您需要以某种方式知道 SubWithProvolone 是否包含 SubWithTurkey —— 或任何其他特定类型的 sub —— 那么你做错了。

我知道最好的例子是 Java I/O 类 InputStream, OutputStream, Reader, 和 Writer 和他们所有的子类。任何 InputStream 都可以通过用 BufferedInputStream 装饰来缓冲;任何 InputStream 都可以通过用 InputStreamReader 装饰来用作 Reader等等..

但是,如果您开始引入诸如 "a BufferedWriter must not be decorated by another BufferedWriter"(假设的)之类的规则,那么它就会崩溃——没有很好的方法来执行它,事实上,没有 善意的需要规则。如果您正在尝试对一种情况进行建模,而这种情况确实需要有关如何组成组件的规则,那么 Decorator 就不是一个很好的选择。

  1. What if two ingredients are dependant. For example, if you order lettuce THEN you need some other kind of vegetable to make a "valid" Sub.

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

  1. When is it better to use the decorator pattern instead of a SubSandwich class with an ArrayList of Ingredient? I know here the Ingredients don't add behaviour, which makes the Subway example not accurate, but let's assume they do.

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

  1. Why extending? Why not using interfaces?

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

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

装饰器模式的意图与您的应用程序的意图不匹配。装饰器不是您用例的正确模式。

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

The decorator pattern can be used to extend (decorate) the functionality of a certain object statically, or in some cases at run-time, independently of other instances of the same class, provided some groundwork is done at design time

你错过了 Decorator 意图中的“indenpendently”这一点。如果要建立关联或互斥,Decorator是没有用的。

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

您可以完成这些 SE 问题:

When would you use the Builder Pattern?

Builder Pattern in Effective Java