Fork me on GitHub

装饰者模式-Decorator Pattern

解决的问题:

我们在装饰新家的时候买了几幅抽象画,买回来之后发现有些加上色彩艳丽的边框更适合我们,而有的加上玻璃罩之后更能符合我们的使用。那我们来怎么解决这个问题呢?他需要动态的给别的对象增加额外的职责,这就是装饰者模式的目的。

我们可以通过继承的方式来给原对象增加新功能,但是装饰者模式采用组合的方式比生成子类更加灵活。

类图及样例实现:

Decorator-Pattern

在装饰模式中的各个角色有:

抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。

具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。

装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。

具体装饰(Concrete 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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#include <string>
#include <iostream>
#include <memory>
using namespace std;

//抽象类Tank
class Tank
{
public:
virtual void shot()=0;
virtual void run()=0;

public:
virtual ~Tank()
{
cout<<"in the destructor of Tank"<<endl;
}
};
//具体类 T50
class T50:public Tank
{
public:
void shot()
{
cout<<"Tank T50 shot()"<<endl;
}
void run()
{
cout<<"Tank T50 run()"<<endl;
}
public:
virtual ~T50()
{
cout<<"In the destructor of T50"<<endl;
}
};
//具体类T75
class T75:public Tank
{
public:
void shot()
{
cout<<"Tank T75 shot()"<<endl;
}
void run()
{
cout<<"Tank T75 run()"<<endl;
}
public:
virtual ~T75()
{
cout<<"In the destructor of T75"<<endl;
}
};

//抽象类,Decorator
class Decorator:public Tank
{
protected:
Tank* tank;
public:
Decorator(Tank* tank):tank(tank) {} //具体的坦克的装饰类
virtual ~Decorator()
{
cout<<"In the destructor of Decorator"<<endl;
}
public:
void shot()
{
tank->shot();
}
void run()
{
tank->run();
}
};

class InfraredDecorator: public Decorator
{
private:
string infrared;//这就是所谓的addAtrribute
public:
InfraredDecorator(Tank* tank):Decorator(tank) {}
virtual ~InfraredDecorator()
{
cout<<"in the destructor of InfraredDecorator"<<endl;
}
public:
void set_Infrared(const string &infrared)
{
this->infrared=infrared;
}
string get_infrared() const
{
return infrared;
}
void run()
{
tank->run();
set_Infrared("+Infrared");
cout<<get_infrared()<<endl;
}
void shot()
{
tank->shot();
}
};

class AmphibianDecorator:public Decorator
{
private:
string amphibian;
public:
AmphibianDecorator(Tank* tank):Decorator(tank) {}
~AmphibianDecorator()
{
cout<<"in the destructor of AmphibianDecorator"<<endl;
}
public:
void set_amphibian(const string &hibian)
{
this->amphibian=hibian;
}
string get_amphibian() const
{
return amphibian;
}
public:
void run()
{
tank->run();
set_amphibian("+amphibian");
cout<<get_amphibian()<<endl;
}
void shot()
{
tank->shot();
}
};

int main(int argc, char **argv)
{
//给T50增加红外功能
Tank* tank1(new T50);
Tank* pid1(new InfraredDecorator(tank1));
pid1->shot();
cout<<endl;
pid1->run();
cout<<endl;
cout<<endl<<"---------------"<<endl;
//给t75增加红外、两栖功能
Tank* tank2(new T75);
tank2->run();
Tank* pid2(new InfraredDecorator(tank2));
Tank* pad2(new AmphibianDecorator(pid2));
pad2->shot();
cout<<endl;
pad2->run();
cout<<endl;
cout<<endl<<"--------------"<<endl;

//动态撤销其他装饰 ?
tank2->run();

Tank * tank3(tank2);
tank3->run();
return 0;
}

装饰者与适配者模式的区别:

1.关于新职责:适配器也可以在转换时增加新的职责,但主要目的不在此。装饰者模式主要是给被装饰者增加新职责的。

2.关于原接口:适配器模式是用新接口来调用原接口,原接口对新系统是不可见或者说不可用的。装饰者模式原封不动的使用原接口,系统对装饰的对象也通过原接口来完成使用。(增加新接口的装饰者模式可以认为是其变种–“半透明”装饰者)

3.关于其包裹的对象:适配器是知道被适配者的详细情况的(就是那个类或那个接口)。装饰者只知道其接口是什么,至于其具体类型(是基类还是其他派生类)只有在运行期间才知道。

要点:

1. 装饰者和被装饰对象有相同的超类型。

2. 可以用一个或多个装饰者包装一个对象。

3. 装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的。

4. 对象可以在任何时候被装饰,所以可以在运行时动态的,不限量的用你喜欢的装饰者来装饰对象。

5. 装饰模式中使用继承的关键是想达到装饰者和被装饰对象的类型匹配,而不是获得其行为。

6. 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。在实际项目中可以根据需要为装饰者添加新的行为,做到“半透明”装饰者。

适用场景与优缺点:

在以下情况下应当使用装饰模式:

1.需要扩展一个类的功能,或给一个类增加附加责任。

2.需要动态地给一个对象增加功能,这些功能可以再动态地撤销。

3.需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。

优点:

1.Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。

2.通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

缺点:

1.这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。

2.装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。

3.装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。