【问题标题】:Decorator pattern: enhanced design?装饰器模式:增强设计?
【发布时间】:2018-02-02 12:09:43
【问题描述】:

我正在阅读“Head first design patterns book”中提供的关于装饰模式的示例。

我注意到两件事:

  1. 如果您需要从封装的装饰器堆栈中移除一个装饰器,则必须逐个遍历组件引用,这是 O(n) 复杂度。
  2. 从概念上讲,我发现将基本组件包装(封装)到装饰器对象中是错误的。应该颠倒过来;组件对象应该封装装饰对象。

我是设计模式的新手,很有可能我错了。请向我解释一下我的思维方式有什么特别的问题,以便我可以学习。

我创建了一个不同的设计,它解决了我提到的问题。也许他们会增加新的问题;请随时指出问题。

这是建议的UML图:

基本上我所做的是在 Component 类中创建了一个字典,用于保存已添加的装饰器,并使 Decorator 抽象类不从组件继承自接口(因此组件抽象类)。

通过这种方式,我们可以移除任何我们想要的装饰,复杂度为 O(1),并且更符合逻辑地构造为组件包装装饰器的方式,而不是反之。

我知道我可能没有注意到原始装饰图案设计的一些优势。请给我建议。

这是我的代码url

编辑:

客户何时需要移除装饰器的示例:

例如说客户选择调味品,他正在添加鞭子,去除焦糖,每次查看总价格如何变化,基于客户选择添加为装饰者。

【问题讨论】:

  • 首先,您能否详细说明一下为什么您认为在 StarBuzz 案例中动态删除装饰器会很有用? (我可以想象一个原因,但如果你是明确的,你的问题会更好)。其次,您认为 StarBuzz 场景中的典型 n 类型是什么?我猜 max(n) 可能是 20,所以迭代这么多迭代器不太可能成为现代计算机的问题。
  • 我在主要问题中包含了这个例子,它解释了客户何时需要移除装饰器,但我想我开始明白你的意思了,可以扭曲的装饰器的最大数量是 20 ,所以是的,对于这个数量使用 O(n) 解决方案是完全合乎逻辑的,请将其写为答案,以便我接受。

标签: c# design-patterns decorator composition


【解决方案1】:
  1. 如果您需要从包装的装饰器堆栈中删除一个装饰器,则必须逐个遍历组件引用,这是 O(n) 复杂度。

确实,从理论上讲,在包装后移除装饰器会更复杂。但是,您需要考虑什么是可能的 n。我将猜测它提出的装饰器模式,可能有一个小的 (max(n) == 20) 迭代器。迭代这么多不会是一个实际的问题。

  1. 从概念上讲,我发现将基本组件包装(封装)到装饰器对象中是错误的。应该颠倒过来;组件对象应该封装装饰对象。

装饰器模式寻求添加功能(通过具体的装饰器),而无需修改组件类。在某些情况下,组件无法更改(例如,它来自标准库)。使用您建议的方法,必须修改组件以封装其装饰器。这不是 Decorator 的本意。

original GoF book 中,设计模式对他们解决的问题以及设计模式的后果(并非都是积极的!)有明确的定义.根据您的复杂点(删除装饰器),作者提到了这个结果:

  1. 很多小对象。 使用装饰器的设计通常会导致系统由许多看起来相似的小对象组成。这些对象的不同之处仅在于它们相互连接的方式,而不在于它们的类或变量的值。尽管这些系统很容易被理解它们的人定制,但它们可能很难学习和调试。

【讨论】:

    【解决方案2】:
    1. 为什么需要“从扭曲的装饰器堆栈中删除装饰器”?这是什么意思?我认为您混淆了两个不同的概念,装饰器模式和堆栈。第一种是面向对象编程中的一种设计模式,后者是一种数据结构。

    2. 装饰器模式的存在使得新功能可以添加到基础组件中,而无需重新定义使用/依赖它的组件。这就是装饰器组件“封装”基础组件的原因,以便它可以使用它包含的任何功能,同时添加其他所需的功能。如果基础组件封装了装饰器组件,您将如何引用其中任何一个给定的功能?按照你的例子,想象我打电话给Mocca.GetCost()。如果它没有被覆盖也没有重新定义,CondimentDecorated.GetCost() 将被调用,我想,考虑到你想要做什么,它会调用Beverage.GetCost()。这种方法会做什么?遍历字典以查找要调用的装饰器方法?这没有任何意义,因为在调用CondimentDecorated.GetCost() 时,您只会再次调用Beverage.GetCost()。如您所说,如果您可以从装饰器字典中“删除您想要的任何装饰”,那么所有这些将如何工作?那么,当您致电Mocca.GetCost() 时,会有什么行为?

    这并不是说您尝试做的事情是不可能的,而是很好地质疑为什么某事是这样的。但是这里有很多关于 OOP 的误解和违规行为。意思是,不仅要质疑如何使事情变得更好,还要质疑为什么会以现在的方式完成。

    【讨论】:

    • 回答您的问题: 1. 比如说客户正在选择调味品,他正在添加鞭子,去除焦糖,每次查看总价格如何变化,基于客户选择的内容添加为装饰器。 2.不,我没有混淆数据结构和装饰器模式中的堆栈,我使用了“堆栈”这个词,因为我开始看到每个新的装饰对象都位于旧对象之上,也许它更像是链表。 3.我可以通过遍历字典并调用接口方法来引用其中任何一个中存在的功能。
    • 在我的 UML 图中,Decorator Abstract 类不是从 Component Abstract 类继承,但两者都继承自 ICost 接口,这是两个类之间的常见关联。最后,它没有在装饰器对象中引用组件对象,而是在组件对象中保存了一个装饰器字典
    • what the client is choosing to be added as a decorator这句话我没看懂。 each new decorating object is siting on top of the old object 你的意思是每个装饰器实例都可以引用基础组件的一个实例?如果是这样,是的,您还会如何引用它的功能? calling the interface method调用接口方法就是调用实现接口的类的方法。那么,这一切将如何运作?
    • Isaac ,如果你愿意(或者即使你有时间),请检查我的代码,然后告诉我这是否违反了很多 OOP,我想我无法正确解释我想要什么说。
    • 我认为您的设计违反了单一职责原则,这是一个 OOPS 违规行为。饮料类将有自己的业务逻辑(它应该处理什么责任),除了这个提议的设计还分配了维护装饰器的额外责任,遍历它们并计算成本。我鼓励您查看装饰器模式的 Head First 实现并回复它是如何处理 SRP 的。我真的很欣赏你的学习方式。继续前进,继续前进。
    猜你喜欢
    • 2010-10-05
    • 1970-01-01
    • 2013-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-19
    相关资源
    最近更新 更多