关于装饰模式和抽象装饰类的问题?

oym*_*oym 32 java oop design-patterns decorator

这个问题被问已经在这里,但不是回答具体问题,的装饰图案作品是如何给予,而不是描述.我想再次提出这个问题,因为通过阅读装饰器模式的工作方式,我的答案并不是很明显(我已经阅读了维基百科的文章和Head First Design Patterns一书中的部分).

基本上,我想知道为什么必须创建一个实现(或扩展)某个接口(或抽象类)的抽象装饰器类.为什么所有新的"装饰类"都不能简单地实现(或扩展)基本抽象对象本身(而不是扩展抽象装饰类)?

为了使这个更具体,我将使用设计模式书中处理咖啡饮料的例子:

  • 有一个名为的抽象组件类 Beverage
  • 简单的饮料类型,如HouseBlend简单的延伸Beverage
  • 为了装饰饮料,CondimentDecorator创建了一个扩展Beverage并具有实例的抽象类Beverage
  • 假设我们想要添加"牛奶"调味品,Milk创建一个扩展的类CondimentDecorator

我想理解为什么我们需要这个CondimentDecorator类,以及为什么类Milk不能简单地扩展Beverage类本身并Beverage在其构造函数中传递一个实例.

希望这很清楚...... 如果不是,我只想知道为什么这个模式需要抽象装饰器类?谢谢.

编辑:我试图实现这一点,省略了抽象装饰器类,它似乎仍然有效.这个抽象类是否存在于此模式的所有描述中,仅仅因为它为所有新装饰类提供了标准接口?

Mik*_*kis 31

迟到一年半比没有好:

某个接口的装饰器的基类不是必需的,但是它非常有用.

它是有用的,一方面作为一种记录方法,从它派生的类是有问题的接口的装饰器,但主要是因为装饰器通常不需要为装饰接口的每个方法添加功能,所以一个基础装饰器类允许派生装饰器只实现它们实际需要添加某些功能的接口的那些方法,将其余的方法留给基类来提供默认实现.(只是简单地将呼叫委托给死者.)

将此与从头开始实现装饰接口的编写装饰器进行对比,其中编译器要求您为接口的每个单独方法提供实现,无论装饰器是否将向其添加任何功能.

这很简单,真的.

  • 那么为什么装饰者不能是装饰者的子类呢?因此,我们消除了对提供非装饰实现(简单委托)的抽象装饰器的需要,并且如果使用新方法添加装饰器,装饰器仍然可以使用而无需修改它(添加委托)! (2认同)

Raf*_*afa 11

(你的问题有点晚了..)

我也花了很长时间试图找出答案.在我的例子中,非具体的Decorator扩展了要装饰的类("decoree"),而不是decoree和Decorator共有的接口.

从不同来源阅读后,在我看来,除了tvanfosson所说的,让混凝土装饰器扩展一个抽象或更一般的装饰器的原因是我们不会一遍又一遍地重复"委托"代码.

[Beverage]<———————[(abstract) CondimentDecorator]<—————[Milk]
[decoree ]——————<>[ adds code to efficiently    ]
                  [ forward calls to decorated  ]<—————[LemonJuice]
                  [ instance of Beverage        ]
Run Code Online (Sandbox Code Playgroud)

在您的情况下,您的抽象装饰器将扩展decoree并实现与decoree相同的接口,委托/转发所有方法调用.然后,你可以构建的具体装饰器只需要实现那些你想要做一些不同的方法,而不仅仅是将方法调用转发给decoree.

我希望我很清楚......: - S

就个人而言,我对在装饰器中重复decoree界面的需要感到有点失望.这增加了一些耦合,因为在decoree的界面发生变化的任何时候(比如获得更多方法),装饰者需要赶上.

在PHP 5.3.2中(是的,我知道你的问题与Java有关),但是,应该可以动态捕获所有方法调用并将它们全部转发,而不需要Decorator需要知道调用哪些方法(在但是,这是一种非常有效的方法,必须使用Reflection API).我想这已经可以在Ruby和其他语言中实现.

PS:这是我在SO中的第一个回答!^ _ ^


Jer*_*emy 11

我想知道同样的事情.回到源代码,GOF设计模式,我在装饰器章节的"实现"中看到了这一点:

"省略抽象的装饰类,没有必要定义一个抽象的装饰类时,你只需要添加一个责任,这是经常发生的情况,当你处理现有的类层次结构,而不是设计一个新的.在这种情况下,您可以将Decorator的责任合并到将组件的请求转发到Concrete Decorator中."

所以至少在这种情况下,似乎GOF同意你:-)

我不确定"一个责任"的含义是什么.我不知道,如果超过"一个责任"意味着有一个以上的责任或一个以上的混凝土装饰混凝土一个装饰,每一个都有一个责任.无论哪种方式,我都不明白为什么抽象的装饰器是必要的.我的猜测是,tvanfosson的回答(在他自己的答案评论)是正确的 - 一旦你开始创建一些装饰类的,它澄清超类的设计之下决定将它们分组.在另一方面,如果只有一个班,这或许使得设计决策不太清楚,如果你只是坐在作为基础元件和装饰(之间无谓的中间人第二类话说回来,这是相当有可能增加你想要在某些时候添加更多,

无论如何,似乎与设计清晰有关,而不是代码工作与否之间的区别.


tva*_*son 9

它可以使用不同组合的各种装饰器独立地装饰基类,而不必为每种可能的组合派生类.例如,假设你想要Beverage牛奶和肉豆蔻.使用基于抽象装饰器类的装饰器,你只需用with MilkNutmegdecorators 包装.如果它来自Beverage,你必须有一个MilkWithNutmegBeverage类,一个MilkBeverage类和一个NutmegBeverage类.您可以想象随着可能组合的数量增加,这会如何爆炸.使用抽象装饰器实现将每个装饰减少到一个装饰器类实现.

  • 我不确定我理解.是的,我从"饮料"中获取"牛奶"; 但'Milk'也被传递给'Beverage'类的一个实例,我认为这个实例允许我用抽象装饰器类来包装类. (4认同)
  • 我想你可以这样做,但作为一个子类,它不需要提供任何额外的行为.从概念上讲,即使它实现了相同的接口,装饰器也不是**实际上是装饰类的实例,而是一个增强装饰类的辅助类.基于不同的抽象类公开了这个设计方面,并使得更清楚地添加新的装饰.将装饰作为子类实现模糊了这种区别,并使实现对于可能在不知不觉中破坏装饰模式的子类开放. (3认同)
  • @ es11:好的,所以你从"饮料"中获取牛奶 - 那么'肉豆蔻'呢?如果消费者试图调用Nutmeg.Drink()或更糟,Nutmeg.PutOutFire()会发生什么?如果Nutmeg是饮料,那么它应该能够作为饮料*,即使它不被用作饮料的装饰者.*通过使Nutmeg成为抽象装饰的实例,你可以防止它被误认为是抽象类. (2认同)

And*_*rew 5

基础装饰器可以更轻松地创建其他装饰器。想象一下,Beverage 有几十个抽象方法,或者是一个接口,比如stir()、getTemperature()、drink()、pour() 等。然后你的装饰者都必须实现这些方法,只是为了将它们委托给包装好的饮料,并且你的 MilkyBeverage 和 SpicyBeverage 都拥有所有这些方法。

相反,如果您有一个具体的 BeverageDecorator 类,它通过简单地将每次调用委托给包装的 Beverage 来扩展或实现 Beverage,则子类可以扩展 BeverageDecorator 并仅实现它们关心的方法,而让基类来处理委托。

如果 Beverage 类(或接口)获得新的抽象方法,这也可以保护您:您需要做的就是将该方法添加到 BeverageDecorator 类。没有它,您将不得不将该方法添加到您创建的每个装饰器中。