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

时间:2010-01-06 22:24:55

标签: java design-patterns oop decorator

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

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

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

  • 有一个名为Beverage
  • 的抽象组件类
  • 简单饮品类型(例如HouseBlend)只会扩展Beverage
  • 要装饰饮料,会创建一个抽象的CondimentDecorator类,其扩展Beverage并具有Beverage的实例
  • 假设我们要添加“牛奶”调味品,则会创建一个扩展Milk
  • 的班级CondimentDecorator

我想了解为什么我们需要CondimentDecorator类以及为什么类Milk不能简单地扩展Beverage类本身并传递{{{1}的实例1}}在它的构造函数中。

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

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

9 个答案:

答案 0 :(得分:29)

比从来没有迟到一年半的时间:

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

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

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

就这么简单,真的。

答案 1 :(得分:10)

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

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

在从不同来源阅读之后,在我看来,除了tvanfosson said之外,让具体装饰器扩展抽象或更一般的装饰器的原因是我们不重复“委托” “一遍又一遍地编码。

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

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

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

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

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

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

答案 2 :(得分:10)

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

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

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

我不确定“一个责任”的含义是什么。我不确定“一个责任”是否意味着一个具有多个责任或多个具体装饰者的具体装饰者,每个人都有一个责任。无论哪种方式,我都不明白为什么抽象的装饰器是必要的。我的猜测是,tvanfosson的答案(在他对自己的答案的评论中)是正确的 - 一旦你开始创建一些装饰类,它就明确了将它们归入超类的设计决策。另一方面,在只有一个类的情况下,如果你添加第二个类作为基础组件和装饰器之间无意义的中间人,那么它可能会使设计决策变得不那么清晰(尽管如此,它很可能是你想在某些时候添加更多,所以即使在单个案例中也可以更好地包括抽象装饰......)

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

答案 3 :(得分:9)

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

答案 4 :(得分:4)

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

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

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

答案 5 :(得分:2)

实际上我有时也会忽略这个'中间人'抽象(如果你没有很多装饰组合)。它解耦更多,但也增加了复杂性。在我看来,装饰背后的主要思想是在自己的实现中包装接口。

答案 6 :(得分:2)

在Decorator模式的情况下,继承用于类型匹配。这不是分类的典型原因。子类化的正常原因是继承行为

为了明确这一区别,使用CondimentDecorator对Beverage进行子类化是有意义的,因为CondimentDecorator类区分了饮料实现(如DarkRoast)和调味品(如Mocha)之间的区别。例如,考虑一下,您的任务是通过查看代码来为StarBuzz提供一个菜单。你会立即知道哪些是“基础”饮料和哪种调味品作为基类。 DarkRoast的基类是饮料。 Mocha的基类是CondimentDecorator。

尽管如此,我认为在Java中将Beverage抽象类实现为接口可能更有意义。根据该书,代码没有使用这种方法,因为StarBuzz已经有了一个抽象的Beverage类(p 93),抽象基础组件是实现模式的历史方法。

答案 7 :(得分:1)

BaseDecorator(CondimentDecorator)类强制ConcreteDecortor(Milk)在构造函数的输入中有一个基本的abtract类(Brevrage)。

如果BaseDecorator类不存在,则不会强制您实现需要输入中的BaseAbstractClass的装饰器。装饰模式的目标是强制输入和输出类。

(我想你以Head First Design Pattern为例,在他们的例子中,BaseDecorator类没有这个构造函数。)

答案 8 :(得分:0)

也许是为时已晚,但这是单一责任的gof陈述的重点。

假设抽象装饰器还有其他方法不属于组件(抽象)那里由decarator具有多重责任。

这确保组件始终可重用,因为它应该实现组件的所有抽象/接口,但不能实现装饰器的额外责任。

任何使用该组件(抽象)的程序都可以确保组件在利用方式(运行对象)和组件的创建(抽象)中相应地工作。因此,这将确保程序的完整性,易于开发和扩展应用程序/程序。

因此,装饰者将离开那些alove然后好像作为他自己的对象以任何可能的方式使用包括类似于上面的方式。 例如。装饰师正在装饰。(只是一个想法)

不是很棒吗? (拍手)