65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.69/5 (5投票s)

2014年5月17日

CPOL

8分钟阅读

viewsIcon

17913

新的一天,新的模式。今天我们将开始深入讨论结构型模式,首先从装饰器模式开始。

新的一天,新的模式。今天我们将开始深入讨论结构型模式,首先从装饰器模式开始。

确实,我正在努力收集关于这个宏大主题的尽可能多的资源,以使其有价值并增加一些之前未发表的内容。因此,我正在同时阅读著名的《四人帮》一书,以及《O’Reilly 出版的 C# 3.0 设计模式》以及其他一些教程和资料。

我喜欢 O’Reilly 书中讨论每个模式的方式,所以我会重复使用每个模式的标题,但我会努力为内容增加更多价值。那么,让我们开始讨论装饰器模式。

1 – 角色

装饰器模式的角色是提供一种动态添加新状态和行为的方法。被装饰的对象不知道自己正在被装饰,这对于不断发展的系统非常有用。

2 – 插图

顾名思义,它用于在运行时添加或删除(装饰)具有新状态或新行为的对象。我读过的说明这个模式的最佳例子是照片管理系统,它允许您编辑照片的某些方面,例如添加新边框、标签或标题。

考虑我们有一张照片,我们想给它添加一个边框,也许是边框和标题,或者只是标题。如果您仔细思考一下,您会发现照片和边框的组合会产生另一个对象,即带有边框的照片。因此,每次组合都可能在运行时产生一个不同的(未知的)对象,这意味着我们可以有无数种方式来自定义照片。

另外,我喜欢《四人帮》一书中的插图,它是这样的:

假设我们有一个 TextView 对象,它在一个窗口中显示文本。TextView 默认没有滚动条,因为我们不一定总是需要它们。当我们需要的时,我们可以使用 ScrollDecorator 来添加它们。假设我们还想在 TextView 周围添加一个粗黑边框。我们可以使用 BorderDecorator 来添加它。我们只需将装饰器与 TextView 组合即可产生所需的结果。

这个模式的美妙之处在于:

  • 原始对象不知道任何装饰。
  • 没有一个大的、功能齐全的类包含所有选项。
  • 装饰彼此独立。
  • 装饰可以以混搭的方式组合在一起。

3 – 设计

阅读了关于这个模式的各种资料后,我得出结论,由于您使用模式实现的语言特性不同,它的设计会有一些细微的差别。但主要指导方针仍然存在。

我将从一个非常通用的设计(在《四人帮》一书中提到)开始,然后逐步介绍其他变体。

基本设计

参与者

  1. 具体组件 (ConcreteComponent)
    • 可以添加或修改操作的对象类的原始类(可以有多个这样的类)。
  2. 组件 (Component)
    • 标识可以被装饰的对象类的基类(ConcreteComponent 是其中之一)。
  3. 装饰器 (Decorator)
    • 标识装饰器类中通用操作或状态的基类,它继承自 Component 基类。
  4. 具体装饰器 (ConcreteDecorator)
    • 一个继承自 Decorator 基类的类,并添加状态和/或行为(可以有多个这样的类)。
  5. 操作 (Operation)
    • Component 对象中的一个操作,可以被替换(可以有多个操作)。

查看上面的参与者,我们会发现我们有两个基类 Component 和 Decorator。这个模式设计中的大多数变体都源于更改或替换这两个类,正如我们现在将看到的。

如果出现以下情况,请使用此基本设计:

  1. 您不是此对象结构的创建者,例如您已经创建了结构并想对其进行装饰。
  2. 您是创建此结构的人,但您使用的是不支持接口的 C++ 或任何语言。
  3. 您在 Decorator 和 Component 基类中都有默认实现。
  4. 您有多个装饰器在 Decorator 基类中定义了共同的行为和/或状态。

变体 1

正如您从上图注意到的,我们可以看到我们用接口 IComponent 替换了 Component 基类。这里我们必须提到,在《四人帮》一书撰写之初,作者是用 C++ 实现模式的,而 C++ 不包含接口。如今,在 C# 或 Java 等现代语言中,则包含接口的概念。

只要您不想为所有子类添加默认实现,我都建议始终使用接口而不是抽象类。

如果您使用的语言包含接口概念,并且出现以下情况,则使用此变体:

  1. 您在 Component 基类中没有默认实现,并且您为所有具体装饰器都提供了默认实现。
  2. 您是从头开始创建此对象结构的,并且完全控制它。
  3. 您有多个装饰器在 Decorator 基类中定义了共同的行为和/或状态。

变体 2

如果您使用的语言包含接口概念,并且出现以下情况,则使用此变体:

  1. 您在 Component 基类和 Decorator 基类中都没有默认实现。
  2. 您是从头开始创建此对象结构的,并且完全控制它。
  3. 您有多个装饰器在 IDecorator 接口中定义了共同的行为和/或状态。

O’Reilly 出版的 C# 3.0 设计模式》指出 C# 中的 IDecorator 没有意义。我至今仍未完全理解这一点,但我认为,如果您有想要包含在所有装饰器中的共同行为和/或状态,那么您必须使用接口或基类,具体取决于您的情况,您可以从其他变体中选择更合适的方法。

变体 3

如果您使用的语言包含接口概念,并且出现以下情况,则使用此变体:

  1. 您在 Component 基类和 Decorator 基类中都没有默认实现。
  2. 您是从头开始创建此对象结构的,并且完全控制它。
  3. 装饰器之间没有共同的行为和/或状态。

通用设计指南

正如我们在最后四个变体中看到的,您会发现一些我想强调的通用准则:

  1. 所有组件都必须有一个基类或接口。
  2. 所有装饰器都必须是组件。换句话说,所有装饰器都必须继承 Component 基类或实现 IComponent 接口。
  3. 所有装饰器都必须包装一个组件(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 装饰的组件 ===
我喜欢读书。
而且我喜欢踢足球。
而且我喜欢钓鱼。
按任意键继续 . . .

这里需要指出一些重要的注意事项:

  1. 正如我们在最后一个示例中看到的,您可以装饰一个已装饰的组件。
  2. 装饰器模式使您能够灵活地组合不同的装饰器。
  3. 装饰器不需要任何高级语言特性;它们依赖于对象聚合和接口实现。

5 – 最后

现在,我们准备通过研究一些实际示例来深入了解此模式。请继续关注下一篇文章。

© . All rights reserved.