装饰模式的优缺点是什么?

时间:2013-01-25 16:51:31

标签: design-patterns decorator

我正在读这篇文章:

http://www.codeproject.com/Articles/479635/UnderstandingplusandplusImplementingplusDecoratorp

我正在考虑在学校项目中实施这种模式。这不是一个要求,所以我可以减半。但是,我只是认为这是一个扩展我的知识和专业知识的好机会。

学校项目是这样的:创建一个比萨订购应用程序,员工输入客户的订单。所以披萨,它可以有任意数量的浇头。

以上文章(以及 Head First:Design Patterns 一书中的描述)似乎与我的应用程序完美匹配。

这是我的问题:这似乎不是一个好的模式,这就是为什么:

每当“披萨店”在其菜单中添加新的顶部时......他们将不得不添加一个全新的类,并重新编译他们的订购系统并重新分发它们?

我想也许问题是我谷歌的所有例子都必须处理某种食物和配料。

  1. 我是否只是为这种模式找到错误类型的示例?
  2. 有哪些更好的例子可以实现这种模式?
  3. 食品行业是其中之一,它只是实施 那是搞砸了吗?
  4. 这是其中一种模式,但在实际生产代码中几乎没有使用过吗?

4 个答案:

答案 0 :(得分:3)

实际上,Decorator允许您添加一些行为而无需重新编译源代码。您可以在披萨域中声明IPizza接口,并在您的应用程序中使用此抽象。然后,您可以添加另一个程序集,如果IPizza装饰器将具有其他实现,并使用依赖项注入将这些实现注入到您的应用程序中。因此,您无需重新编译应用程序或域。

BTW添加新类比修改现有类更好。在修改之前,你总是可以破坏正常工作的东西。这就是为什么引入单一责任原则的原因。

您的另一个问题:

  1. 不,你找到了Decorator模式使用的好例子(特别是Head First书中的一个)。还可以看一下IO Stream类,它是装饰器的灵感来源。
  2. 模式应该解决问题。如果你没有问题,哪种模式是针对性的,那么它只是错误地使用模式。
  3. 模式不适合行业。他们坚持你的代码问题(通常是关于代码的重复)
  4. 不,这是好的模式。再想一想.Net中的Streams。是生产代码吗?

答案 1 :(得分:1)

通常在现实世界的应用程序中,您将处理更抽象的对象(即不是比萨饼和咖啡的东西:))

Decorator模式的真实示例是Java BufferedReader类。例如,它为FileReader添加了附加功能。

好处是你可以在运行时更改行为,而不是让你拥有许多不同的对象。

在您的示例中,如果我有四个对象:

Pizza
Tomatoes
Cheese
Mushrooms

然后我可以用这四种成分的任意组合制作披萨。否则我将不得不有大量的类来允许这种行为,例如PizzaWithTomatoesAndCheesePizzaWithTomatoesAndMushrooms

答案 2 :(得分:0)

我可以想到的有关装饰器模式的“缺点”是设计是刚性的。

装饰对象时,由于要实现精简的通用接口,因此您通常对对象不了解很多。初次实现该接口时,对于您的算法而言可能是合适的,但是更改可能会出现问题。

考虑this example有关绘画程序。我喜欢它,因为它说明了我在模式中看到的两个问题。

装饰器之一是填充形状。如果您唯一了解形状是通用界面,您打算如何计划填充形状?至少如果它是一个仅绘制矩形的程序,则可以在保持相同类设计的情况下完成一些不太丑陋的事情,但是绘制矩形似乎是一个人为的例子……

第二,绘画形状时可以做几件事?基本上只是不同类型的边框和不同类型的填充。也许阴影和文字。关键是,如果可能的装饰只有一种或两种,那么Decorator可能是过度设计的解决方案。

我认为,一个更重要的问题是每个包装器和对象都执行其动作(抽象Decorator类中target方法的实现是delegate.operation())。因此,当需求发生变化并且我们不应该再一个接一个地执行另一个需求,而另一个应该取代另一个需求时,或者包装器的行为应取决于包装对象的某些值时,会发生什么情况。

正如您所说,我怀疑几乎没有使用过这种模式,因为您需要稳定的需求,几种类型的装饰器来证明开销和真正的包装行为(其中被包装者的价值始终无关紧要)。

实际上,我能找到的唯一明智的示例是Java核心库中的示例,例如InputStream。

您提到的《 Head First》一书中出现了另一个荒谬的例子,他们用它来计算咖啡的价格,在那里他们创建了一系列的类来计算咖啡的价格(每个类都专门使用在总费用中添加新费用!)

对我来说,这表明很难在野外找到这种模式的合法用途。

答案 3 :(得分:0)

我仅出于一个原因同意使用Decorator模式:

    代码更改管理方案中的
  • SRP。请参见有效使用旧版代码的第72页上的出色示例。当您希望在单元测试上下文中对更改进行划分(特别是使用易碎的旧版代码)时,Decorator很棒。这对于分离关注点非常有用,但是像所有此类更改一样,它是有代价的(请参见下文)。

在更一般的情况下,出于多种原因,我不同意使用Decorator模式:

  • 这可能会很慢,尤其是在代码的性能关键部分中使用时。重复 许多装饰器的嵌套会导致所生成的程序集中重复JMP,这可能会对性能产生严重影响。
  • 当您必须读取/解析一个多重嵌套的修饰符,并弄清其执行顺序(以堆栈形式表示的预顺序或后顺序深度优先遍历)时,这就像剥洋葱层一样甚至两者的混合。)
  • 这种想法“哇,您可以使用装饰器来组合任何喜欢的排列组合!”可通过其他方式解决,例如在继承中偏向于组合,在此我们穷举组合struct或类,使它们可以容纳该类所需的所有组合可能性;例如游戏中的玩家角色可能拥有自己驾驶的枪支和/或车辆;为了知道它们是什么,我们为这两个字段提供了字段(它们可以是嵌套的struct或指向其他struct类的指针/引用,分别分配),无论是否使用。这在游戏的实体组件系统中很常见。
  • 避免了类的泛滥,这对您而言可能不是好事。

最终,合成是相当专业的,并且忽略了SRP,但是到最后,大多数代码都是出于专门目的而不是无休止的重用而编写的。

最终,我很乐意在主动开发/原型开发功能时使用Decorator,但目的是在功能集建立之后再将其切换为更具体的硬编码解决方案。