文章

23种设计模式-装饰器模式

Decorator模式 装饰器模式

Intent/目的

动态地为对象附加额外的责任。装饰器为扩展功能提供了一种灵活的替代方案,而不是通过子类化

Also Known As

Wrapper

Motivation/动机

有时我们希望给单个对象添加新的职能,而不是整个类。例如,图形用户界面工具包应该允许您向任何用户界面组件添加属性(如边框)或行为(如滚动)。

一种添加职能的方法是通过继承。从另一个类继承边框会将边框放置在每个子类实例周围。然而,这种方式是不灵活的,因为边框的选择是静态确定的。客户端无法控制何时以及如何为组件装饰边框。

一种更灵活的方法是将组件封装在另一个对象中,并在其中添加边框。封装对象称为装饰器。装饰器符合其装饰的组件的接口,因此对于组件的客户端来说,其存在是透明的。装饰器将请求转发给组件,并在转发之前或之后执行额外的操作(例如绘制边框)。透明性使您可以递归地嵌套装饰器,从而允许添加无限数量的职能。

例如,假设我们有一个TextView对象,用于在窗口中显示文本。默认情况下,TextView没有滚动条,因为我们可能并不总是需要它们。当我们需要时,我们可以使用一个 ScrollDecorator 来添加滚动条。假设我们还想在文本视图周围添加一个粗黑边框。我们也可以使用一个 BorderDecorator 来添加这个。我们只需将装饰器与TextView组合起来,就可以得到所需的结果

以下对象图显示了如何将 TextView 对象与 BorderDecoratorScrollDecorator 对象组合,以产生一个带边框、可滚动的文本视图:

ScrollDecoratorBorderDecorator 类是 Decorator 的子类,Decorator 是一个抽象类,用于装饰其他可视化组件的可视化组件。

VisualComponent 是可视对象的抽象类。它定义了它们的绘制和事件处理接口。请注意,Decorator 类只是简单地将绘制请求转发给它的组件,而 Decorator 的子类可以扩展这个操作。

装饰器子类可以自由添加特定功能的操作。例如,ScrollDecoratorScrollTo 操作允许其他对象滚动界面,如果它们知道界面中恰好有一个 ScrollDecorator 对象。这种模式的重要方面是,它允许装饰器出现在任何 VisualComponent 可以出现的地方。这样,客户端通常无法区分装饰过的组件和未装饰过的组件之间的区别,因此它们完全不依赖于装饰。

Applicability/应用场景

  • 动态透明地向个别对象添加职能,即在不影响其他对象的情况下。
  • 对于可以撤回的职能。
  • 当通过子类扩展不可行时。有时可能存在大量独立的扩展,并且会产生大量子类来支持每种组合。或者类定义可能被隐藏或以其他方式无法进行子类化。

Structure/结构

Participants/角色

  • Component (VisualComponent)
    • 定义了可以动态添加职能的对象的接口。
  • ConcreteComponent (TextView)
    • 定义了一个对象,可以附加额外的职能。
  • Decorator
    • 维护对组件对象的引用,并定义了符合组件接口的接口。
  • ConcreteDecorator (BorderDecorator, ScrollDecorator)
    • 向组件添加附加的职能。

Consequences/总结

装饰器模式有一下两种优点和两种缺点

  1. 比静态继承更灵活。装饰器模式提供了一种比静态(多重)继承更灵活的方式来向对象添加责任。通过装饰器,可以通过简单地附加和分离它们来在运行时添加和移除责任。相比之下,继承需要为每个额外的责任创建一个新的类(例如,BorderedScrollableTextViewBorderedTextView)。这导致产生许多类并增加系统的复杂性。此外,为特定的组件类提供不同的装饰器类让您可以混合和匹配责任。装饰器还使得很容易两次添加一个属性。例如,要给一个文本视图添加双重边框,只需附加两个 BorderDecorators。而至少从最好的角度来看,从 Border 类继承两次容易出错。
  2. 避免在层次结构的高层中使用功能繁多的类。装饰器提供了一种按需添加职能的方法。与尝试在一个复杂的可定制类中支持所有可预见的特性不同,您可以定义一个简单的类,并通过装饰器对象逐步添加功能。功能可以从简单的部分组合而成。因此,应用程序不需要为它不使用的功能付费。而且,很容易独立地为未预见的扩展定义新种类的装饰器。扩展一个复杂的类往往会暴露与您要添加的责任无关的细节。
  3. 装饰器和其组件并不相同。装饰器充当一个透明的封装。但从对象标识的角度来看,一个经过装饰的组件与组件本身并不相同。因此,当您使用装饰器时,不应依赖于对象标识。
  4. 大量小对象。使用装饰器的设计通常导致由许多外观相似的小对象组成的系统。这些对象只在它们的连接方式上有所不同,而不是它们的类或变量的值。尽管这些系统很容易被那些理解它们的人定制,但它们可能很难学习和调试。

Adapter(适配器):装饰器与适配器的不同之处在于,装饰器仅改变对象的责任,而不改变其接口;而适配器会给一个对象提供一个全新的接口。

Composite(组合):装饰器可以被视为一个只有一个组件的退化的组合。然而,装饰器增加了额外的责任——它并不旨在进行对象聚合。

Strategy(策略):装饰器允许您改变对象的外表;而策略允许您改变对象的内部。这两种是改变对象的替代方式。

本文由作者按照 CC BY 4.0 进行授权

© Poul.Y. 保留部分权利。