深入了解结构模式 :: 装饰器模式 :: 第一部分






3.69/5 (5投票s)
新的一天,新的模式。今天我们将开始深入讨论结构型模式,首先从装饰器模式开始。
新的一天,新的模式。今天我们将开始深入讨论结构型模式,首先从装饰器模式开始。
确实,我正在努力收集关于这个宏大主题的尽可能多的资源,以使其有价值并增加一些之前未发表的内容。因此,我正在同时阅读著名的《四人帮》一书,以及《O’Reilly 出版的 C# 3.0 设计模式》以及其他一些教程和资料。
我喜欢 O’Reilly 书中讨论每个模式的方式,所以我会重复使用每个模式的标题,但我会努力为内容增加更多价值。那么,让我们开始讨论装饰器模式。
1 – 角色
装饰器模式的角色是提供一种动态添加新状态和行为的方法。被装饰的对象不知道自己正在被装饰,这对于不断发展的系统非常有用。
2 – 插图
顾名思义,它用于在运行时添加或删除(装饰)具有新状态或新行为的对象。我读过的说明这个模式的最佳例子是照片管理系统,它允许您编辑照片的某些方面,例如添加新边框、标签或标题。
考虑我们有一张照片,我们想给它添加一个边框,也许是边框和标题,或者只是标题。如果您仔细思考一下,您会发现照片和边框的组合会产生另一个对象,即带有边框的照片。因此,每次组合都可能在运行时产生一个不同的(未知的)对象,这意味着我们可以有无数种方式来自定义照片。
另外,我喜欢《四人帮》一书中的插图,它是这样的:
假设我们有一个 TextView 对象,它在一个窗口中显示文本。TextView 默认没有滚动条,因为我们不一定总是需要它们。当我们需要的时,我们可以使用 ScrollDecorator 来添加它们。假设我们还想在 TextView 周围添加一个粗黑边框。我们可以使用 BorderDecorator 来添加它。我们只需将装饰器与 TextView 组合即可产生所需的结果。
这个模式的美妙之处在于:
- 原始对象不知道任何装饰。
- 没有一个大的、功能齐全的类包含所有选项。
- 装饰彼此独立。
- 装饰可以以混搭的方式组合在一起。
3 – 设计
阅读了关于这个模式的各种资料后,我得出结论,由于您使用模式实现的语言特性不同,它的设计会有一些细微的差别。但主要指导方针仍然存在。
我将从一个非常通用的设计(在《四人帮》一书中提到)开始,然后逐步介绍其他变体。
基本设计
参与者
- 具体组件 (ConcreteComponent)
- 可以添加或修改操作的对象类的原始类(可以有多个这样的类)。
- 组件 (Component)
- 标识可以被装饰的对象类的基类(ConcreteComponent 是其中之一)。
- 装饰器 (Decorator)
- 标识装饰器类中通用操作或状态的基类,它继承自 Component 基类。
- 具体装饰器 (ConcreteDecorator)
- 一个继承自 Decorator 基类的类,并添加状态和/或行为(可以有多个这样的类)。
- 操作 (Operation)
- Component 对象中的一个操作,可以被替换(可以有多个操作)。
查看上面的参与者,我们会发现我们有两个基类 Component 和 Decorator。这个模式设计中的大多数变体都源于更改或替换这两个类,正如我们现在将看到的。
如果出现以下情况,请使用此基本设计:
- 您不是此对象结构的创建者,例如您已经创建了结构并想对其进行装饰。
- 您是创建此结构的人,但您使用的是不支持接口的 C++ 或任何语言。
- 您在 Decorator 和 Component 基类中都有默认实现。
- 您有多个装饰器在 Decorator 基类中定义了共同的行为和/或状态。
变体 1
正如您从上图注意到的,我们可以看到我们用接口 IComponent 替换了 Component 基类。这里我们必须提到,在《四人帮》一书撰写之初,作者是用 C++ 实现模式的,而 C++ 不包含接口。如今,在 C# 或 Java 等现代语言中,则包含接口的概念。
只要您不想为所有子类添加默认实现,我都建议始终使用接口而不是抽象类。
如果您使用的语言包含接口概念,并且出现以下情况,则使用此变体:
- 您在 Component 基类中没有默认实现,并且您为所有具体装饰器都提供了默认实现。
- 您是从头开始创建此对象结构的,并且完全控制它。
- 您有多个装饰器在 Decorator 基类中定义了共同的行为和/或状态。
变体 2
如果您使用的语言包含接口概念,并且出现以下情况,则使用此变体:
- 您在 Component 基类和 Decorator 基类中都没有默认实现。
- 您是从头开始创建此对象结构的,并且完全控制它。
- 您有多个装饰器在 IDecorator 接口中定义了共同的行为和/或状态。
《O’Reilly 出版的 C# 3.0 设计模式》指出 C# 中的 IDecorator 没有意义。我至今仍未完全理解这一点,但我认为,如果您有想要包含在所有装饰器中的共同行为和/或状态,那么您必须使用接口或基类,具体取决于您的情况,您可以从其他变体中选择更合适的方法。
变体 3
如果您使用的语言包含接口概念,并且出现以下情况,则使用此变体:
- 您在 Component 基类和 Decorator 基类中都没有默认实现。
- 您是从头开始创建此对象结构的,并且完全控制它。
- 装饰器之间没有共同的行为和/或状态。
通用设计指南
正如我们在最后四个变体中看到的,您会发现一些我想强调的通用准则:
- 所有组件都必须有一个基类或接口。
- 所有装饰器都必须是组件。换句话说,所有装饰器都必须继承 Component 基类或实现 IComponent 接口。
- 所有装饰器都必须包装一个组件(Decorator 必须有一个类型为 Component 或 IComponent 的成员)。
4 – 实现
我将在此尝试为您提供装饰器模式的一个简单实现。我将讨论如何实现装饰器模式的通用方法,而不考虑任何具有挑战性的示例或复杂场景。
我将使用变体 3 来通过以下 UML 类图为您提供一个简单的实现。
使用 C# 或其他候选语言,首先我们必须创建一个 IComponent 接口,它将只包含一个操作。
1 2 3 4 | public interface IComponent { void Operation(); } |
然后,我们将创建一个实现 IComponent 接口的 Component 类。
1 2 3 4 5 6 7 | public class Component : IComponent { public void Operation() { Console.WriteLine( "我喜欢读书。" ); } } |
现在我们要用装饰器来装饰 Component 对象。所以,让我们创建一个装饰器类,它将包装一个 IComponent 对象并实现相同的 IComponent 接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class DecoratorA : IComponent { private IComponent _component; public Decorator(IComponent component) { _component = component; } public void Operation() { _component.Operation(); Console.WriteLine( "我还喜欢踢足球。" ); } } |
另一个装饰器类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class DecoratorB : IComponent { private IComponent _component; public Decorator(IComponent component) { _component = component; } public void Operation() { _component.Operation(); Console.WriteLine( "我还喜欢钓鱼。" ); } } |
现在,让我们构建客户端,它将是一个简单的控制台应用程序,将使用我们的 Component 并对其进行装饰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | using System; namespace DecoratorPatternImplementation { class Program { static void Main( string [] args) { Console.WriteLine( "=== 原始组件 ===" ); IComponent component = new Component(); component.Operation(); Console.WriteLine(); Console.WriteLine( "=== 被 DecoratorA 装饰的原始组件 ===" ); IComponent decoratorA = new DecoratorA(component); decoratorA.Operation(); Console.WriteLine(); Console.WriteLine( "=== 由 DecoratorB 装饰的原始组件 ===" ); IComponent decoratorB = new DecoratorB(component); decoratorB.Operation(); Console.WriteLine(); Console.WriteLine( "=== 由 DecoratorA 装饰,再由 DecoratorB 装饰的组件 ===" ); IComponent hybridDecorator = new DecoratorB(decoratorA); hybridDecorator.Operation(); Console.WriteLine(); } } } |
运行控制台应用程序,输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | === 原始组件 === 我喜欢读书。 === 由 DecoratorA 装饰的原始组件 === 我喜欢读书。 而且我喜欢踢足球。 === 由 DecoratorB 装饰的原始组件 === 我喜欢读书。 而且我喜欢钓鱼。 === 由 DecoratorA 装饰,再由 DecoratorB 装饰的组件 === 我喜欢读书。 而且我喜欢踢足球。 而且我喜欢钓鱼。 按任意键继续 . . . |
这里需要指出一些重要的注意事项:
- 正如我们在最后一个示例中看到的,您可以装饰一个已装饰的组件。
- 装饰器模式使您能够灵活地组合不同的装饰器。
- 装饰器不需要任何高级语言特性;它们依赖于对象聚合和接口实现。
5 – 最后
现在,我们准备通过研究一些实际示例来深入了解此模式。请继续关注下一篇文章。