【设计模式 - 结构型模式】4. 装饰模式
在现实生活中,常常需要对现有产品增加新的功能或美化其外观,如房子装修、相片加相框等,这些都可以使用装饰模式。它的主要作用就是:
- 增强一个类原有的功能。
- 为一个类添加新的功能。
并且装饰模式也不会改变原有的类。
在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰模式来实现。
一、定义与特点
装饰(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
主要优点有:
- 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用;
- 通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果;
- 装饰器模式完全遵守开闭原则。
其主要缺点是:装饰模式会增加许多子类,过度使用会增加程序得复杂性。
二、结构与实现
通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。
如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,就可以在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式。下面来分析其基本结构和实现方法。
装饰模式的结构
装饰模式主要包含以下角色:
- 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
装饰模式的结构图如下图所示:
装饰模式的实现代码如下:
#include <iostream>
using namespace std;
// 抽象构件角色
class Component {
public:
Component() {}
virtual void operation() {}
};
// 具体构件角色
class ConcreteComponent: public Component {
public:
ConcreteComponent() {
cout << "创建具体构件角色" << endl;
}
void operation() {
cout << "调用具体构件角色的方法operation()" << endl;
}
};
// 抽象装饰角色
class Decorator: public Component {
public:
Decorator(Component *component) {
this->component = component;
}
void operation() {
component->operation();
}
public:
Component *component;
};
// 具体装饰角色
class ConcreteDecorator: public Decorator {
public:
// 关键要加上后面的Decorator(component)
ConcreteDecorator(Component *component):Decorator(component) { }
void operation() {
component->operation();
addedFunction();
}
void addedFunction() {
cout << "为具体构件角色增加额外的功能addedFunction()" << endl;
}
};
int main()
{
Component *com = new ConcreteComponent();
com->operation();
cout << "---------------------------------" << endl;
Component *dec = new ConcreteDecorator(com);
dec->operation();
return 0;
}
程序运行结果如下:
创建具体构件角色
调用具体构件角色的方法operation()
---------------------------------
调用具体构件角色的方法operation()
为具体构件角色增加额外的功能addedFunction()
可以看到,ConcreteDecorator 装饰给 ConcreteComponent 类添加了动作 addedFunction。
三、应用实例
3.1 用于增强功能的装饰模式
我们用程序来模拟一下戴上装饰品提高我们颜值的过程:
#include <iostream>
using namespace std;
// 抽象构件角色:颜值接口
class IBeauty {
public:
IBeauty() {}
virtual int getBeautyValue() {}
};
// 具体构件角色:Me类
class Me: public IBeauty {
public:
// @Override
int getBeautyValue() {
return 100;
}
};
// 具体装饰角色:戒指装饰类
class RingDecorator: public IBeauty {
public:
RingDecorator(IBeauty *me) {
this->me = me;
}
// @Override
int getBeautyValue() {
return me->getBeautyValue() + 20;
}
public:
IBeauty *me;
};
int main()
{
IBeauty *me = new Me();
cout << "我原本的颜值:" << me->getBeautyValue() << endl;
IBeauty *meWithRing = new RingDecorator(me);
cout << "戴上了戒指后,我的颜值:" << meWithRing->getBeautyValue() << endl;
return 0;
}
运行程序,输出如下:
我原本的颜值:100
戴上了戒指后,我的颜值:120
这就是最简单的增强功能的装饰模式。以后我们可以添加更多的装饰类,比如:
// 具体装饰角色:耳环装饰类
class EarringDecorator: public IBeauty {
public:
EarringDecorator(IBeauty *me) {
this->me = me;
}
// @Override
int getBeautyValue() {
return me->getBeautyValue() + 50;
}
private:
IBeauty *me;
};
// 具体装饰角色:项链装饰类
class NecklaceDecorator: public IBeauty {
public:
NecklaceDecorator(IBeauty *me) {
this->me = me;
}
// @Override
int getBeautyValue() {
return me->getBeautyValue() + 50;
}
private:
IBeauty *me;
};
int main()
{
IBeauty *me = new Me();
cout << "我原本的颜值:" << me->getBeautyValue() << endl;
// 随意挑选装饰
IBeauty *meWithRing = new RingDecorator(me);
cout << "戴上了戒指后,我的颜值:" << meWithRing->getBeautyValue() << endl;
// 多次装饰
IBeauty *meWithManyDecorators = new NecklaceDecorator(new RingDecorator(new EarringDecorator(me)));
cout << "戴上耳环、戒指、项链后,我的颜值:" << meWithManyDecorators->getBeautyValue() << endl;
// 任意搭配装饰
IBeauty *meWithNecklaceAndRing = new NecklaceDecorator(new RingDecorator(me));
cout << "戴上戒指、项链后,我的颜值:" << meWithNecklaceAndRing->getBeautyValue() << endl;
return 0;
}
运行程序,输出如下:
我原本的颜值:100
戴上了戒指后,我的颜值:120
戴上耳环、戒指、项链后,我的颜值:220
戴上戒指、项链后,我的颜值:200
可以看到,装饰器也实现了 IBeauty 接口,并且没有添加新的方法,也就是说这里的装饰器仅用于增强功能,并不会改变 Me 原有的功能,这种装饰模式称之为透明装饰模式,由于没有改变接口,也没有新增方法,所以透明装饰模式可以无限装饰。
装饰模式是继承的一种替代方案。本例如果不使用装饰模式,而是改用继承实现的话,戴着戒指的 Me 需要派生一个子类、戴着项链的 Me 需要派生一个子类、戴着耳环的 Me 需要派生一个子类、戴着戒指 + 项链的需要派生一个子类......各种各样的排列组合会造成类爆炸。而采用了装饰模式就只需要为每个装饰品生成一个装饰类即可,所以说就增加对象功能来说,装饰模式比生成子类实现更为灵活。
3.2 用于添加功能的装饰模式
我们用程序来模拟一下房屋装饰粘钩后,新增了挂东西功能的过程:
#include <iostream>
using namespace std;
// 抽象构件角色:颜值接口
class IHouse {
public:
virtual void live() {}
};
// 具体构件角色:房屋类
class House: public IHouse {
public:
// @Override
void live() {
cout << "房屋原有的功能:居住功能" << endl;
}
};
// 抽象装饰角色:粘钩装饰器接口
class IStickyHookHouse: public IHouse {
public:
virtual void hangThings() {}
};
// 具体装饰角色:粘钩装饰类
class StickyHookDecorator: public IStickyHookHouse {
public:
StickyHookDecorator(IHouse *house) {
this->house = house;
}
// @Override
void live() {
house->live();
}
// @Override
void hangThings() {
cout << "有了粘钩后,新增了挂东西功能" << endl;
}
private:
IHouse *house;
};
int main()
{
IHouse *house = new House();
house->live();
// 新增功能
IStickyHookHouse *stickyHookHouse = new StickyHookDecorator(house);
stickyHookHouse->live();
stickyHookHouse->hangThings();
return 0;
}
运行程序,显示如下:
房屋原有的功能:居住功能
房屋原有的功能:居住功能
有了粘钩后,新增了挂东西功能
这就是用于新增功能的装饰模式。我们在接口中新增了方法:hangThings,然后在装饰器中将 House 类包装起来,之前 House 中的方法仍然调用 house 去执行,也就是说我们并没有修改原有的功能,只是扩展了新的功能,这种模式在装饰模式中称之为半透明装饰模式。
为什么叫半透明呢?由于新的接口 IStickyHookHouse 拥有之前 IHouse 不具有的方法,所以我们如果要使用装饰器中添加的功能,就不得不区别对待装饰前的对象和装饰后的对象。也就是说客户端要使用新方法,必须知道具体的装饰类 StickyHookDecorator,所以这个装饰类对客户端来说是可见的、不透明的。而被装饰者不一定要是 House,它可以是实现了 IHouse 接口的任意对象,所以被装饰者对客户端是不可见的、透明的。由于一半透明,一半不透明,所以称之为半透明装饰模式。
我们可以添加更多的装饰器:
// 抽象装饰角色:镜子装饰器的接口
class IMirrorHouse: public IHouse {
public:
virtual void lookMirror() {}
};
// 具体装饰角色:镜子类
class MirrorDecorator: public IMirrorHouse {
public:
MirrorDecorator(IHouse *house) {
this->house = house;
}
// @Override
void live() {
house->live();
}
// @Override
void lookMirror() {
cout << "有了镜子后,新增了照镜子功能" << endl;
}
private:
IHouse *house;
};
int main()
{
IHouse *house = new House();
house->live();
IMirrorHouse *mirrorHouse = new MirrorDecorator(house);
mirrorHouse->live();
mirrorHouse->lookMirror();
return 0;
}
运行程序,输出如下:
房屋原有的功能:居住功能
房屋原有的功能:居住功能
有了镜子后,新增了照镜子功能
现在我们仿照透明装饰模式的写法,同时添加粘钩和镜子装饰试一试:
int main()
{
IHouse *house = new House();
house->live();
IStickyHookHouse *stickyHookHouse = new StickyHookDecorator(house);
IMirrorHouse *houseWithStickyHookMirror = new MirrorDecorator(stickyHookHouse);
houseWithStickyHookMirror->live();
houseWithStickyHookMirror->hangThings(); // 这里会报错,找不到 hangThings 方法
houseWithStickyHookMirror->lookMirror();
return 0;
}
我们会发现,第二次装饰时,无法获得上一次装饰添加的方法。原因很明显,当我们用 IMirrorHouse 装饰器后,接口变为了 IMirrorHouse,这个接口中并没有 hangThings 方法。
那么我们能否让 IMirrorHouse 继承自 IStickyHookHouse,以实现新增两个功能呢?
可以,但那样做的话两个装饰类之间有了依赖关系,那就不是装饰模式了。装饰类不应该存在依赖关系,而应该在原本的类上进行装饰。这就意味着,半透明装饰模式中,我们无法多次装饰。
只要添加了新功能的装饰模式都称之为 半透明装饰模式,他们都具有不可以多次装饰的特点。仔细理解上文半透明名称的由来就知道了,“透明”指的是我们无需知道被装饰者具体的类,既增强了功能,又添加了新功能的装饰模式仍然具有半透明特性。
参考: