定义
适配器模式(Adapter Pattern) 是一种结构型设计模式,它允许将一个类的接口转换成客户端所期望的另一种接口。适配器模式通常用于将已有的类或遗留系统中的接口与新系统或目标接口进行兼容,从而能够在系统中无缝地使用不兼容的类。
特性
- 接口转换:适配器模式通过转换接口,使得不兼容的接口可以一起工作。
- 包装现有对象:适配器模式不修改现有对象的代码,而是通过创建适配器类来“包装”现有对象,使其符合目标接口的要求。
适用场景
适配器模式适用于以下场景:
- 接口不兼容:当你需要使用一个已有的类或系统,但它的接口不符合你的需求时,可以通过适配器模式来转换接口。
- 遗留系统:如果有一个遗留系统,它的接口无法直接与新的系统兼容,适配器可以帮助使遗留系统与新系统一起工作。
- 第三方库:如果集成了第三方库,并且库的接口与系统不兼容,可以通过适配器模式将库的接口适配成需要的接口。
类设计
适配器模式通常包括以下几个角色:
1.Target(目标接口):定义客户端所需要的接口,通常是期望的接口。
2.Adaptee(遗留接口):需要被适配的现有接口,通常是与目标接口不兼容的接口。
3.Adapter(适配器):将 Adaptee 接口转换成 Target 接口的类,可以通过继承或组合实现。
4.Client(客户端):使用 Target 接口的对象,客户端通过 Target 接口与适配器进行交互。
代码实现解析
我们通过一个简单的例子来演示适配器模式。在这个例子中,我们有一个 旧的接口 IAdaptee 和一个 新的接口 ITarget,并且需要使用一个适配器来使客户端可以通过新的接口使用旧的接口。
优点
- 促进了类之间的协同工作,即使它们没有直接的关联。
- 提高了类的复用性。
- 增加了类的透明度。
- 提供了良好的灵活性。
缺点
- 过度使用适配器可能导致系统结构混乱,难以理解和维护。
1. 定义目标接口(新接口)
class ITarget {
public:
virtual void process() = 0; // 新接口方法
};
ITarget 是客户端期望的接口,process() 方法是客户端需要调用的方法。
2. 定义遗留接口(老接口)
class IAdaptee {
public:
virtual void foo(int data) = 0; // 老接口方法
virtual int bar() = 0; // 老接口方法
};
IAdaptee 是遗留的接口,里面有两个方法:foo() 和 bar(),这些方法是旧系统或类提供的接口。
3. 旧类实现遗留接口
class OldClass : public IAdaptee {
public:
void foo(int data) override {
// 实际业务逻辑
cout << "OldClass::foo(" << data << ")" << endl;
}
int bar() override {
// 实际业务逻辑
cout << "OldClass::bar()" << endl;
return 42; // 返回示例数据
}
};
OldClass 是实现了 IAdaptee 接口的类,它的接口与客户端所期望的接口不兼容。
4. 适配器实现(对象适配器)
class Adapter : public ITarget {
protected:
IAdaptee* pAdaptee; // 组合旧接口的实例
public:
Adapter(IAdaptee* pAdaptee) : pAdaptee(pAdaptee) {}
void process() override {
// 将新接口的调用转发给旧接口的调用
int data = pAdaptee->bar(); // 调用旧接口的 bar()
pAdaptee->foo(data); // 调用旧接口的 foo()
}
};
对象适配器:在这个实现中,Adapter 类继承了 ITarget 接口,并持有 IAdaptee 的实例。
它将 process() 方法的调用转发到旧接口的 bar() 和 foo() 方法。
组合方式:适配器类通过组合方式引用旧接口类,而不是继承。
5. 客户端调用
int main() {
IAdaptee* pAdaptee = new OldClass(); // 创建旧接口对象
ITarget* pTarget = new Adapter(pAdaptee); // 创建适配器对象
// 客户端通过适配器调用新接口
pTarget->process(); // 调用适配器的 process(),内部会调用旧接口的方法
delete pAdaptee;
delete pTarget;
return 0;
}
客户端通过 Adapter 来访问 OldClass,而不需要直接与 OldClass 交互。
客户端只与 ITarget 接口交互。
6. 类适配器实现
适配器也可以通过 多继承 实现,这称为 类适配器。通过继承旧接口,适配器类可以同时继承目标接口和遗留接口。
class Adapter : public ITarget, protected OldClass { // 多继承
public:
void process() override {
int data = bar(); // 直接调用旧接口的方法
foo(data); // 直接调用旧接口的方法
}
};
类适配器:在类适配器中,Adapter 类继承了 OldClass,
它不仅实现了 ITarget 接口,而且还继承了 OldClass,可以直接调用 OldClass 的方法。
完整代码:
//目标接口(新接口)
class ITarget{
public:
virtual void process()=0;
};
//遗留接口(老接口)
class IAdaptee{
public:
virtual void foo(int data)=0;
virtual int bar()=0;
};
//遗留类型
class OldClass: public IAdaptee{
//....
};
//对象适配器
class Adapter: public ITarget{ //继承
protected:
IAdaptee* pAdaptee;//组合
public:
Adapter(IAdaptee* pAdaptee){
this->pAdaptee=pAdaptee;
}
virtual void process(){
int data=pAdaptee->bar();
pAdaptee->foo(data);
}
};
//类适配器
class Adapter: public ITarget,
protected OldClass{ //多继承
}
int main(){
IAdaptee* pAdaptee=new OldClass();
ITarget* pTarget=new Adapter(pAdaptee);
pTarget->process();
}
class stack{
deqeue container;
};
class queue{
deqeue container;
};
总结
Adapter 模式的优点:
- 解耦:客户端与旧接口解耦,客户端通过目标接口与适配器交互,不直接与遗留系统进行交互。
- 复用:适配器模式可以使旧系统或类重用,而不需要修改旧代码。
- 适应性强:代理类可以灵活适应不同的接口需求,尤其在接口不兼容时尤为有用。
Adapter 模式的缺点:
- 增加类的数量:引入适配器类增加了系统的复杂度和类的数量,可能导致代码维护复杂度增加。
- 性能开销:适配器可能会引入额外的性能开销,尤其是当有多个适配器时。
适用场景:
- 接口不兼容的类:当现有类的接口与目标接口不兼容时,使用适配器模式可以将接口转换成目标接口,确保客户端可以正常使用。
- 遗留系统的集成:将遗留系统与新系统进行集成时,适配器模式可以帮助适配旧系统的接口。
- 第三方库集成:在使用第三方库时,如果库的接口与系统不兼容,适配器模式可以将库的接口适配成系统需要的接口。
总结
适配器模式通过提供一个代理类来将不兼容的接口转换成客户端需要的接口,确保了客户端与系统之间的解耦。通过适配器,客户端可以在不修改现有系统代码的前提下,使用不兼容的类或接口。