前言
结构型模式关注如何组合对象以形成更大的结构,提供了一种将对象组合成具有特定行为的结构的最佳方式。本章介绍结构型模式中的适配器模式和装饰器模式。
6.适配器模式(Adapter)
问题
需要两个不兼容的接口(类或者对象)能够在一起工作。
解决方案
适配器模式通过引入一个适配器充当中间人的方式,将其中一个对象转换成能够与另一个对象兼容的新的对象,使得两个不兼容的对象可以协同工作。可以理解为,适配器就是一个110V转220V的变压器,可以让你在美国使用中国的家用电器。
在C++中,适配器模式一般通过继承或组合来实现,通常包含以下三个角色:
- 目标接口(Target interface):客户端所期望的接口。客户端代码通过目标接口与适配器进行交互。
- 适配者(需要适配的接口,Adaptee):拥有一个或多个特定功能的接口,这些接口可能与目标接口不适配,导致客户端代码不能直接调用这些需要的功能。
- 适配器(Adapter):实现了目标接口,并在其内部实现对适配者特定功能的调用,从而间接实现客户端代码对适配者功能的调用。
根据实现的方式不同,适配器一般分为对象适配器和类适配器两种。
对象适配器
通过组合的方式实现。适配器持有一个适配者的引用,并在实现目标接口的过程中调用适配者的方法来完成功能调用。更符合面向对象的设计原则。
#include <iostream>
using namespace std;
// 目标类
class Voltage220 {
public:
virtual void Charge220() {
cout << "使用220v插座充电." << endl;
}
};
// 需要适配的类
class Voltage110 {
public:
virtual void Charge110() {
cout << "使用110v插座充电." << endl;
}
};
// 适配器
class Adapter : public Voltage220 {
Voltage110* m_v110;
public:
Adapter(Voltage110* v110) {
this->m_v110 = v110;
}
void Charge220() override {
cout << "110v转换为220v." << endl;
m_v110->Charge110();
}
};
int main() {
Voltage110* v110 = new Voltage110();
Voltage220* v220 = new Adapter(v110);
v220->Charge220();
return 0;
}
类适配器
通过多重继承的方式实现。适配器同时继承两个对象的接口。同样在适配器类中实现目标接口的方法,并调用适配者类中需要的方法。这种方式的缺点是可能违反里氏替换原则,即子类必须能够替换父类。
#include <iostream>
using namespace std;
// 目标类
class Voltage220 {
public:
virtual void Charge220() {
cout << "使用220v插座充电." << endl;
}
};
// 需要适配的类
class Voltage110 {
public:
virtual void Charge110() {
cout << "使用110v插座充电." << endl;
}
};
// 适配器
class Adapter : public Voltage220, public Voltage110 {
public:
Adapter() {}
void Charge220() override {
cout << "110v转换为220v." << endl;
Charge110();
}
};
int main() {
Voltage220* v220 = new Adapter();
v220->Charge220();
return 0;
}
应用场景
1. 系统集成: 需要将两个系统或组件集成在一起,但是他们的接口不兼容时,可以创建一个适配器来实现。
2. 旧代码兼容: 需要重构部分代码或者替换某些类库等情况下,如果新的接口与旧的接口不兼容,可以通过适配器进行兼容。
3. 接口标准化: 当需要将多个系统或组件的接口标准化为统一的接口方便用户切换使用时,可以创建一个适配器,实现标准接口,但在内部使用不同的系统或组件的接口。
4. 扩展功能: 在不改变现有类的结构的情况下,可以通过适配器为类添加新的功能,提高了代码的可扩展性。
优缺点
优点:
- 提高了类的可复用性和可扩展性: 可以最大程度地复用现有代码而不需要做出改变,避免大规模改写现有代码。
- 解耦目标类和适配器类: 符合开闭原则,提高了代码的灵活性(不想要了可以直接删掉适配器类而不影响原有功能)。
缺点:
- 增加了系统的复杂性。
- 降低了代码的可读性: 外面看到调用的是A类的接口,其实内部使用的是B类的方法。
6. 装饰器模式(Decorator)
问题
在某些情况下,我们需要在不修改现有类的情况下,动态地添加功能或责任。直接修改现有类可能会影响到其他部分的代码,如果使用继承,每次新增功能都需要继续继承现有的类,如此就会使继承的层次越来越深,不利与代码的维护和可读性。
解决方案
装饰器模式通过创建一个装饰类来包装原始类,提供了一种在运行时动态地为对象添加新功能的方法。装饰类具有与原始类相同的接口,它内部包含一个指向原始对象的引用,并且可以根据需要包装额外的功能。这样,你可以通过组合不同的装饰类来构建出具有不同功能组合的对象。
基本角色:
- 组件:是一个接口类或抽象类,定义了对象的核心功能。
- 具体组件:继承自组件接口,实现了组件接口或抽象类的具体类,是被装饰的对象。
- 装饰器:是一个接口或抽象类,继承自组件接口,并持有一个组件对象的引用。
- 具体装饰器:继承自装饰器,实现了装饰器接口或抽象类的具体类,用来给组件添加额外的功能。
// 买个车
#include <iostream>
#include <string>
using namespace std;
// 抽象组件
class AbstractCompent {
public:
virtual void Show() = 0;
string pStr;
};
// 具体组件
class Car :public AbstractCompent {
public:
Car() {
this->pStr = "能开的车";
}
void Show() override {
cout << pStr << endl;
}
};
// 抽象装饰器类
class AbstractDecrate : public AbstractCompent {
public:
virtual void Show() = 0;
protected:
AbstractCompent* pBase;
};
// 高配版(具体装饰器类)
class HighEndCar :public AbstractDecrate {
public:
HighEndCar(AbstractCompent* pBase) {
this->pBase = pBase;
}
void Show() {
this->pStr = pBase->pStr + " + 倒车影像 + 仿皮座椅";
cout << this->pStr << endl;
}
};
// 豪华版(具体装饰器类)
class LuxuryCar :public AbstractDecrate {
public:
LuxuryCar(AbstractCompent* pBase) {
this->pBase = pBase;
}
void Show() {
this->pStr = pBase->pStr + " + 全景影像 + 真皮座椅";
cout << this->pStr << endl;
}
};
int main() {
Car* car = new Car;
car->Show();
HighEndCar* hcar = new HighEndCar(car);
hcar->Show();
LuxuryCar* lcar = new LuxuryCar(car);
lcar->Show();
return 0;
}
应用场景
1. 扩展功能: 允许在不修改源代码的前提下,给已存在的类添加新的功能。符合开闭原则。
2. 动态行为组合: 同一个对象可以在不同时间点被不同的装饰器装饰,从而实现不同的功能。如果一个对象有多种可选的行为,并且这些行为可能会动态改变,那么使用装饰器是个很好的选择。
3. API增强: 当提供API给第三方进行调用时,装饰器可以用于添加额外的功能,比如日志记录、安全校验等,而调用者不需要知道具体的细节。
4. 权限管理: 用不同的装饰器封装功能,可以用来控制原有接口的访问权限。
5. 缓存机制: 在网络请求或数据库查询等操作中,装饰器可以用来添加额外的缓存、重试、超时处理等功能。
(总结:不改变源代码的情况下,添加额外的功能!)
优缺点
优点:
- 可以动态地添加或删除对象的功能,无需修改原有的代码。
- 不影响现有对象的结构,符合开闭原则。
- 可以使用多个装饰器对象来组合多种功能。
缺点:
- 使系统中增加额外的类变量。
- 装饰器对象与原始对象之间的关系过于复杂,降低代码可读性。
…
To be continued.