装饰器模式与子分类

Posted

技术标签:

【中文标题】装饰器模式与子分类【英文标题】:Decorator pattern versus sub classing 【发布时间】:2011-06-18 02:19:56 【问题描述】:

我可以通过添加子类来解决添加功能的问题,那么为什么要使用装饰器模式,装饰器模式的真正优势是什么?

【问题讨论】:

您可以在here 中看到关于这两种模式的示例,解决了GoF book 中的相同问题(关于TextView)。 只需加上我的 2 美分:阅读下面的答案时,您可以记住,装饰器 DP 可以简化子类的管理。您可以单独使用它们,在它们之间创建不同的组合,等等。所有这些都在一个类中:Decorator 类。看一下这个 DP 的 UML 图。例如:cdn.journaldev.com/wp-content/uploads/2013/07/… 为回答这个问题而写的一篇文章:dzone.com/articles/is-inheritance-dead 我喜欢将其分解如下:如果您需要多态行为,则使用对象继承。如果是为了提供附加功能,那么装饰器模式更适合 【参考方案1】:

来自Decorator pattern at wikipedia

装饰器模式可用于 使扩展(装饰)成为可能 特定对象的功能 在运行时

装饰器模式的重点是动态添加额外的行为/功能,这在设计时当然是不可能的。

来自同一篇文章:

装饰器模式是 替代子类化。 子类化在编译时添加行为 时间,并且更改会影响所有 原始类的实例; 装饰可以提供新的行为 单个对象的运行时

【讨论】:

我也必须在设计时创建装饰器类。所以我以类似的方式可以添加子类,这有什么意义。 ***上的文章提供了一些示例。当然你必须在设计时创建装饰器类,你可以选择在运行时动态应用它们。 @Gainster 确实,你在这里是正确的。 Decorator 的最大优势是可以将此类装饰器重用于其他类。正如其他人在此线程中所说的那样。 @Gainster 选择装饰器的原因有很多,其中,1)您可以在同一接口的多个实现上重用相同的装饰器(使用继承,您必须对每个实现进行子类化) , 2) 您可以在被装饰对象的隔离中测试您的装饰器(不能通过继承来模拟父类)。看看:dzone.com/articles/is-inheritance-dead【参考方案2】:

GoF 的一个例子:

假设你有一个 TextView 类。然后在某个地方你想要一个滚动的文本视图,所以你继承 TextView 并创建 ScrolledTextView 类。在其他地方,您需要在文本视图周围设置边框。所以你再次子类化并创建 BorderedTextView。好吧,现在在某个地方你想要边框和滚动两者。前两个子类都没有这两种能力。所以你需要创建第三个。在创建 ScrolledBorderedTextView 时,您实际上是在重复工作。如果你有办法组合前两个的能力,你就不需要这个类。好吧,事情可能会变得更糟,这可能会导致不必要的班级爆炸。

基本上,通过使用装饰器模式,您可以在 RUNTIME 向对象添加任意数量的额外职责,这是通过子类化无法实现的,而不会潜在地损坏您的代码结构或为子类添加许多重复的组合。

但有一件事,设计模式不是你必须使用的东西。 是否需要模式取决于您的特定问题,您是否希望长时间维护代码,是否要扩展以及诸如此类的许多其他因素。 并且没有适用于所有情况的模式。 适合一种情况的模式(装饰器或其他任何东西)可能不是另一种情况的好选择。

【讨论】:

【参考方案3】:

GoF Design Patterns book 确定了使用装饰器相对于子类化的两大优势:

    比静态更灵活 继承。 装饰者模式 提供更灵活的添加方式 对对象的责任比可以 与静态(多个)一起使用 遗产。与装饰者, 可以添加职责和 在运行时删除 连接和分离它们。在 相反,继承需要 为每个创建一个新类 额外的责任(例如, BorderedScrollableTextView, 有边框的文本视图)。这引起 到许多类,并增加 系统的复杂性。此外, 提供不同的装饰器 特定组件的类 类让你混搭 责任。

    装饰器还可以轻松添加 一个属性两次。例如,要 给 TextView 一个双边框, 只需附加两个 BorderDecorator。 从 Border 类继承两次 充其量是容易出错的。

    避免高高在上的功能丰富的类 在层次结构中。装饰器提供了一个 即用即付的添加方法 责任。而不是尝试 支持所有可预见的功能 在一个复杂的、可定制的类中, 你可以定义一个简单的类和 逐步添加功能 装饰器对象。功能可以 由简单的部分组成。作为一个 结果,应用程序无需付费 对于它不使用的功能。它是 也很容易定义新的种类 装饰器独立于 它们扩展的对象类,甚至 对于不可预见的扩展。扩展 一个复杂的类往往会暴露 无关的细节 您正在添加的职责。

在我看来,仅防止子类爆炸就非常引人注目。

如果您有一个TextWindow,您想要添加水平滚动、垂直滚动和边框,所有这些都是独立且可选的,使用子类,您必须为HorizontalScrollingTextWindowVerticalScrollingTextWindow、@987654325 定义子类@、BorderedTextWindowHorizontalScrollingBorderedTextWindowVerticalScrollingBorderedTextWindowHorizontalAndVerticaScrollingBorderedTextWindow 等等,如果您关心滚动和边框的顺序。

使用装饰器,您只需要定义两个滚动装饰器和一个边框装饰器。

【讨论】:

【参考方案4】:

子类化可能会导致Liskov substitution principle 出现问题。装饰器避免了这种情况。

装饰器的另一个优点是您正在(强制写入)到接口。这使测试变得更容易。确实,您的对象层次结构也可以写入接口,因此具有一些相同的优点,但是,我可以单独测试装饰器类的单个实现。我不能对子类做同样的事情,因为我总是会将整个层次结构返回到基类。我无法单独测试新代码。

使用装饰器模式并遵循单一职责原则,我可以创建多个装饰器并随意堆叠它们。我可以在运行时配置它。在继承中,我要么必须创建每个可能的分支(a->b->c 然后 a->c->b,从而复制代码并增加测试数量),要么创建 1 个层次结构,然后在需要时添加另一个层次结构,但是这会触发新的测试/发布周期。

这就是您想要使用装饰器模式而不是子类化的原因。

【讨论】:

这是一个很好的观点......我最初的想法只是为这些类使用接口,但它们不像拥有装饰器那样模块化/可测试。【参考方案5】:

这是基于实际实现的差异。

装饰是子类化的另一种方法,以扩展现有类的功能。这是我们应该使用子类或装饰器的一些场景。

1) 子类化主要用于扩展类似功能的情况 一组想要保留旧功能和新功能的类 在子类中 并且子类的所有实例共享相同的功能。要是我们 进行子类的更改,然后它将反映所有实例 子类。例如 层次关系,类似的类组。

父母->孩子->孙子。

汽车->Maruti 800->Maruti 100(将具有 Maruti 800 和 New 的功能)

2) 装饰器模式用于在不改变旧行为的情况下对现有类进行装饰。例如类圆圈有普通边框但我们需要用红色边框装饰它,一段时间后有些用户想要黄色圆圈和一些用户想要绿色边框的圆圈,有些用户想要红色和黄色边框的圆圈,有些用户想要红色和绿色的边框等等,这是完美的模式,因为它会减少组合类的数量。下面是示例。

Icircle cir=new RedDecorator(new circle()) 用红色装饰圆圈

Icircle cir=new YellowDecorator(new circle()) 用黄色装饰圆圈

Icircle cir=new RedDecorator(new YellowDecorator(new circle())) 装饰

用红色和黄色组成的圆圈,这里我们不需要创建 RedAndYellow 类装饰器。同样的方式,我们可以用其他组合来装饰圆,而无需创建新的组合类。

这样可以减少组合类的数量。

这里是装饰器模式的有用链接

https://www.tutorialspoint.com/design_pattern/decorator_pattern.htm

【讨论】:

【参考方案6】:

灵活性,这就是 IMO 的原因。

【讨论】:

以上是关于装饰器模式与子分类的主要内容,如果未能解决你的问题,请参考以下文章

Python 装饰器和装饰器模式有啥区别?

设计模式之-装饰器模式

《设计模式》之装饰器模式

装饰器模式

装饰器模式:为啥我们需要抽象装饰器? [复制]

java设计模式之装饰器模式