C++设计模式——适配器模式(Adapter Pattern)
微信公众号:幼儿园的学霸
目录
定义
定义
Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
将两个不相兼容的接口通过适配器进行相互转化。
适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。
根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。
作用/解决的问题
它的主要作用是在新接口和老接口之间进行适配。我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。
适配器模式中的角色
Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。该角色把其他类转换为我们期望的接口
Adapter(适配器类):将被适配者和目标接口组合到一起的类,适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
Adaptee(适配者类,被适配者):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,也是希望被改变的接口,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。
其UML类图如下:
思考一个问题:适配器模式中,用到了接口继承?用到了实现继承?什么是接口继承和实现继承?两者有什么区别和联系?
接口继承: 派生类只继承函数接口,也就是声明。
实现继承:派生类同时继承函数的接口和实现。
代码示例
以美式电源接头和国标之间的转换为例,进行说明,代码如下,分别有类适配器模式和对象适配器模式
#include <bits/stdc++.h>
//
//类适配器
//
//Adaptee 被适配者类.现有的类
//比如有美式插座、欧式插座、日式插座
class USOutlet {
public:
void type() {
std::cout << "using US outlet !" << std::endl;
}
};
//Target 抽象目标类
//我们想用的国标类型
class CNOutlet {
public:
virtual void use_CN_type() = 0;
};
//Adapter 适配类
//可以理解为转接头
//采用类适配器
class CNOutletAdapter : public CNOutlet, private USOutlet {
public:
void use_CN_type() override {
//do something convert
std::cout << "adapter transfer CN to US outlet" << std::endl;
type();
}
};
//采用对象适配器
//采用将adaptee作为构造函数参数,类似的方式(如模板等)具有一定的灵活性
//可以选择性适配自己想适配的对象,和策略方式类似
class CNOutletAdapter_2 : public CNOutlet {
private:
std::shared_ptr<USOutlet> m_pUSOutlet;
public:
CNOutletAdapter_2(std::shared_ptr<USOutlet> pUSOutlet)
: m_pUSOutlet(pUSOutlet) {
}
void use_CN_type() override {
//do something convert
std::cout << "adapter transfer CN to US outlet" << std::endl;
m_pUSOutlet->type();
}
};
//对象适配器方法2,将adaptee普通对象作为成员变量
//注意对比和上面对象适配器的区别
class CNOutletAdapter_3 : public CNOutlet {
private:
USOutlet m_usoutlet;
public:
void use_CN_type() override {
//do something convert
std::cout << "adapter transfer CN to US outlet" << std::endl;
m_usoutlet.type();
}
};
int main() {
//类适配器
std::shared_ptr<CNOutlet> pCNOUtlet = std::make_shared<CNOutletAdapter>();
pCNOUtlet->use_CN_type();
//运行结果
//adapter transfer CN to US outlet
//using US outlet !
//对象适配器
CNOutletAdapter_2 cnOutletAdapter2(std::make_shared<USOutlet>());
cnOutletAdapter2.use_CN_type();
//运行结果
//adapter transfer CN to US outlet
//using US outlet !
return 0;
}
在代码中演示的对象适配器模式中,我们在构造的时候将具体需要适配的适配对象传入,这样便可以根据传入不同的对象,从而对该对象进行适配。而类适配器却无法选择对象,它是对整个类进行适配。
总结
优缺点
-
优点:
1.可以让任何两个没有关联的类一起运行;
2.提高了类的复用。通过复用的现存的类, 解决了现存类和复用环境要不一致的问题;
3.低耦合。将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码(遵循开闭原则)。
4.增加了类的透明度。通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的; 5.针对类适配器模式,由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强;
6.针对对象适配器模式,一个对象适配器可以把多个不同的适配者适配到同一个目标。 -
缺点:
1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
适用场景
1.系统需要使用现有的类,而这些类的接口不符合系统的接口要求;
2.两个类所做的事情相同或相似,但是具有不同接口的时候;
总的来说,适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。 此,为区分对象适配器模式和策略模式的重要区别。