0%

设计模式之装饰器模式(Decorator)

一、动机

某些情况下我们可能会”过度的使用继承来扩展对象的功能“,由于继承为类型引入的静态特质(编译时装饰),使得这种扩展缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致子类的膨胀。

如何使得”对象功能的扩展“能够根据需求来动态的实现?同时避免”扩展功能的增多“带来的子类膨胀问题?从而使得任何”功能扩展变化“所导致的影响降为最低。

装饰模式是一种结构型设计模式,允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码以及减少子类个数)。

二、解决方案

当你需要更改一个对象的行为时,其中一个想法就是扩展它所属的类(使用继承)。 但是, 继承可能引发一个严重的问题。

  • 继承是静态的。 你无法在运行时更改已有对象的行为, 只能使用由不同子类创建的对象来替代当前的整个对象。
  • 当子类过多时,将会造成子类代码膨胀。

另一个方法是使用组合。两者的工作方式几乎一模一样:一个对象包含指向另一个对象的引用,并将部分工作委派给引用对象;继承中的对象则继承了父类的行为,它们自己能够完成这些工作。

Inheritance_and_Aggregation

通过采用组合而非继承的手法,Decorator模式实现了再运行时动态扩展对象功能的能力,而且可以根据需求扩展多个功能。避免了使用继承带来的”灵活性差“和”多子类衍生问题”。

Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但是在实现上又表现为has-a Component的组合关系,即Decorator类有使用另外一个Component类。

Decorator模式的目的并非解决”多子类衍生的多继承“问题,其要点在于解决”主体类在多个方向上的扩展功能“——实为”装饰“的含义。

三、装饰器模式的结构

structure_of_Decorator

  1. 部件Component)声明封装器和被封装对象的公用接口。
  2. 具体部件Concrete Component)类是被封装对象所属的类。它定义了基础行为,但装饰类可以改变这些行为。
  3. 基础装饰Base Decorator)类拥有一个指向被封装对象的引用成员变量。该变量的类型应当被声明为通用部件接口,这样它就可以引用具体的部件和装饰。装饰基类会将所有操作委派给被封装的对象。
  4. 具体装饰类Concrete Decorators)定义了可动态添加到部件的额外行为。具体装饰类会重写装饰基类的方法,并在调用父类方法之前或之后进行额外的行为。
  5. 客户端Client)可以使用多层装饰来封装部件,只要它能使用通用接口与所有对象互动即可。
四、Decorator代码示例

穿衣服是使用装饰的一个例子。觉得冷时,你可以穿一件毛衣。如果穿毛衣还觉得冷,你可以再套上一件夹克。如果遇到下雨,你还可以再穿一件雨衣。所有这些衣物都 “扩展” 了你的基本行为,但它们并不是你的一部分,如果你不再需要某件衣物,可以方便地随时脱掉。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <string>
#include <iostream>

// 基本组件,是一个抽象类,声明了一个封装器和被封装对象的公用接口。
class Wear_base {
public:
Wear_base() {}
virtual ~Wear_base() {}
virtual std::string wearing() = 0;
};

// 具体组件类,是被封装对象所属的类。
// 它只定义了基础的行为,但装饰器类可以扩展这一行为。
// 如在本例子中,一个人只穿了内衣。
class Wear_underwear : public Wear_base {
public:
Wear_underwear() {}
virtual ~Wear_underwear() {}

virtual std::string wearing() override {
// 这一行包括下面同一位置处的代码,用于调试时。
// 用于观察运行过程中此接口函数的调用顺序。
std::cout << "Wear_underwear::wearing()" << std::endl;
return "(underwear)";
}
};

// 基本装饰类,拥有一个被封装对象的引用成员变量,应被声明为基本组件的引用。
// 这样它就可以利用多态调用具体组件的接口。
// 同时也继承了基本组件类。用于规范具体装饰类的接口函数。
// 装饰基类会将所有操作委派给被封装的对象。
class DecoratorWear : public Wear_base {
protected:
Wear_base *m_wear_base;

public:
DecoratorWear(Wear_base *wear_base) : m_wear_base(wear_base) {}
virtual ~DecoratorWear() { delete m_wear_base; }

virtual std::string wearing() override {
std::cout << "DecoratorWear::wearing()" << std::endl;
return m_wear_base->wearing();
}
};

// 具体装饰类,其中定义了可动态添加到具体组件的扩展行为。
// 重写了装饰基类的方法,并且在调用装饰基类方法之前进行行为的扩展。
// 本例为扩展穿裤子行为
class Wear_pants : public DecoratorWear {
public:
Wear_pants(Wear_base *wear_base) : DecoratorWear(wear_base) {}
~Wear_pants() {}

std::string wearing() override {
std::cout << "Wear_pants::wearing()" << std::endl;
return "(pants" + DecoratorWear::wearing() + ")";
}
};

// 本例为扩展穿T-恤衫行为
class Wear_sweatshirt : public DecoratorWear {
public:
Wear_sweatshirt(Wear_base *wear_base) : DecoratorWear(wear_base) {}
~Wear_sweatshirt() {}

std::string wearing() override {
std::cout << "Wear_sweatshirt::wearing()" << std::endl;
return "(sweatshirt" + DecoratorWear::wearing() + ")";
}
};

// 本例为扩展穿外套行为
class Wear_jacket : public DecoratorWear {
public:
Wear_jacket(Wear_base *wear_base) : DecoratorWear(wear_base) {}
~Wear_jacket() {}

std::string wearing() override {
std::cout << "Wear_jacket::wearing()" << std::endl;
return "(jacket" + DecoratorWear::wearing() + ")";
}
};

// 客户端,使用通用接口与扩展后的最终对象进行互动。
void clientCode(Wear_base *Wear_base) {
std::cout << Wear_base->wearing() << std::endl;
}

int main() {
// 最开始是只穿了内衣的具体组件对象
Wear_base *w = new Wear_underwear();
// 先穿裤子
Wear_base *d1 = new Wear_pants(w);
// 再穿T-恤衫
Wear_base *d2 = new Wear_sweatshirt(d1);
// 最后穿上外套
Wear_base *d3 = new Wear_jacket(d2);

clientCode(d3);

// DecoratorWear 析构函数里面已经写了释放 delete
// delete w;
// delete d1;
// delete d2;

// 最后形成的对象仍然需要自行 delete
delete d3;
}

Execution result:
Wear_jacket::wearing()
DecoratorWear::wearing()
Wear_sweatshirt::wearing()
DecoratorWear::wearing()
Wear_pants::wearing()
DecoratorWear::wearing()
Wear_underwear::wearing()
(jacket(sweatshirt(pants(underwear))))
五、使用场景
  • 如果你希望在无需修改代码的情况下即可使用对象,且希望在运行时为对象新增额外的行为,可以使用装饰模式。

装饰能将业务逻辑组织为层次结构, 你可为各层创建一个装饰, 在运行时将各种不同逻辑组合成对象。 由于这些对象都遵循通用接口, 客户端代码能以相同的方式使用这些对象。

  • 如果用继承来扩展对象行为的方案难以实现或者根本不可行,你可以使用该模式。

许多编程语言使用 final最终关键字来限制对某个类的进一步扩展。 复用最终类已有行为的唯一方法是使用装饰模式:用封装器对其进行封装。

六、优缺点
  • 你无需创建新子类即可扩展对象的行为。

  • 你可以在运行时添加或删除对象的功能。

  • 你可以用多个装饰封装对象来组合几种行为。

  • 单一职责原则。 你可以将实现了许多不同行为的一个大类拆分为多个较小的类。

  • 在封装器栈中删除特定封装器比较困难。

  • 实现行为不受装饰栈顺序影响的装饰比较困难。

  • 各层的初始化配置代码看上去可能会很糟糕。

七、与其它模式的关系
  • 适配器模式可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。

  • 适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。

  • 责任链模式和装饰模式的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给一系列对象。 但是, 两者有几点重要的不同之处。

    责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为。 此外, 装饰无法中断请求的传递。

  • 组合模式和装饰的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。

    装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。

    但是, 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。

  • 大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。

  • 装饰可让你更改对象的外表, 策略模式则让你能够改变其本质。

  • 装饰和代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。