文章目录
适配器模式(Adapter Pattern)概述
定义:
适配器模式是一种结构型设计模式,它的主要作用是将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。就好像一个 “转换器”,把不匹配的接口进行适配,让它们能够相互对接使用。
适配器模式UML图
适配器模式的结构
目标接口(Target):
这是客户端所期望使用的接口,定义了客户端需要调用的方法集合。它代表了一种规范或者协议,客户端代码是基于这个接口来进行操作的。
适配器(Adapter):
它实现了目标接口,同时在内部持有一个被适配者的实例,并通过调用被适配者的相关方法来实现目标接口中定义的方法,以此来完成接口的转换工作,使得客户端可以通过目标接口来间接使用被适配者的功能。
被适配者(Adaptee):
它是已经存在的、具有特定功能但接口不符合要求的类或对象。其接口可能包含与目标接口相似但不完全相同的方法,或者方法名称、参数等方面存在差异。
作用:
解决接口不兼容问题:在软件开发过程中,经常会遇到需要使用一些现有的类,但这些类的接口与当前系统所期望的接口不一致的情况。适配器模式通过创建一个中间适配器类,对原有类的接口进行包装转换,使其符合新的使用要求,从而避免了对原有类进行大规模修改,保护了已有的代码资产。
提高代码的复用性:可以复用那些已经经过测试、功能完善但接口不符合要求的类,只需要添加适配器类来适配接口,就能让它们融入新的系统环境中继续发挥作用,减少了重复开发的工作量。
增强系统的灵活性和可扩展性:当系统需要接入新的外部组件或者更换底层实现时,如果存在接口不兼容的情况,通过编写适配器就能方便地实现对接,使得系统可以更容易地与不同的外部模块集成,应对各种变化。
C++ 代码示例
以下以一个简单的电源适配器的例子来展示适配器模式在代码中的应用。假设我们有一个手机充电器,它期望的输入电压是 5V,而我们现有的电源输出电压是 220V,需要一个适配器来将 220V 的电压转换为 5V 供手机充电器使用。
// 目标接口,代表手机充电器期望的输入接口
class ChargerTarget{
public:virtual void chargeWith5V() = 0;
};// 被适配者,代表现有的220V电源,有输出220V电压的方法
class PowerSource
{
public:void output220V() {std::cout << "输出220V电压" << std::endl;}
};// 适配器类,将220V电源适配成手机充电器能用的5V电源
class VoltageAdapter : public ChargerTarget{
private:PowerSource* powerSource;
public:VoltageAdapter(PowerSource* source){powerSource = source;}void chargeWith5V() override{powerSource->output220V();std::cout << "经过适配器转换,输出5V电压用于充电" << std::endl;}
};int main()
{PowerSource powerSource;VoltageAdapter adapter(&powerSource);ChargerTarget* charger = &adapter;charger->chargeWith5V();return 0;
}
在上述代码中:
ChargerTarget是目标接口,定义了chargeWith5V方法,代表手机充电器期望的输入接口规范,客户端希望调用这个接口来实现充电功能。
PowerSource是被适配者,它原本有自己的output220V方法,输出的是 220V 电压,不符合手机充电器直接使用的要求。
VoltageAdapter是适配器类,它实现了ChargerTarget接口,内部持有PowerSource对象的指针。在chargeWith5V方法中,先调用PowerSource的output220V方法获取 220V 电压,然后模拟进行转换,输出符合要求的 5V 电压,从而使得原本不匹配的电源和手机充电器能够协同工作,体现了适配器模式的基本原理和应用方式。
除了这种类适配器的形式(通过继承来实现适配),还有对象适配器(通过组合的方式,将被适配者对象作为适配器类的成员变量,代码结构稍有不同,但核心思想一致),可根据具体的应用场景和设计需求来选择合适的实现方式。
C++示例代码2
#include<iostream>
#include<string>
using namespace std;
//抽象球员类
class player
{protected:string m_name;
public:player(string str_name):m_name(str_name){}virtual void attack(){};virtual void defends(){};
};
//具体球员 前锋
class forwords:public player
{
public:forwords(string str_name):player(str_name){}void attack(){cout<<m_name<<"进攻"<<endl;}void defends(){cout<<m_name<<"防守"<<endl;}
};
//中锋
class center:public player
{
public:center(string str_name):player(str_name){}void attack(){cout<<m_name<<"进攻"<<endl;}void defends(){cout<<m_name<<"防守"<<endl;}
};class yaoming
{
private:string m_name;
public:yaoming(string str_name):m_name(str_name){}void attack_yaoming(){cout<<m_name<<"进攻"<<endl;}void defends_yaoming(){cout<<m_name<<"防守"<<endl;}
};class translate:public player
{
private:yaoming *s;
public:translate(string str_name):player(str_name){s = new yaoming("姚明");}void attack(){s->attack_yaoming();}void defends(){s->defends_yaoming();}};int main()
{player *s = new forwords("A");s->attack();player *s1 = new forwords("B");s1->attack();player *m_yaoming = new translate("姚明");m_yaoming->defends();m_yaoming->attack();return 0;
}
应用场景
系统集成与接口兼容
不同数据库的适配:当应用程序需要从一个数据库系统(如 MySQL)切换到另一个数据库系统(如 Oracle)时,由于数据库的 API 接口不同,就可以使用适配器模式。例如,原来的代码是基于 MySQL 的SELECT语句来查询数据,接口可能是mysql_query()函数。当切换到 Oracle 时,其查询接口是OCIStmtExecute()函数。可以创建一个适配器类,实现原来基于 MySQL 接口风格的函数,在内部将其转换为 Oracle 对应的函数调用,这样就可以在不大量修改上层业务逻辑代码的情况下完成数据库的替换。
第三方库集成:
在软件开发中,常常会使用第三方库来实现一些功能。如果第三方库的接口与项目中其他部分的接口不兼容,就可以使用适配器模式来整合。比如,一个图形绘制库的接口是接收坐标点的数组来绘制多边形,而项目中现有的图形数据存储格式是一系列的线段对象。这时可以创建一个适配器,将线段对象转换为坐标点数组的格式,使其能够被图形绘制库所使用。
旧系统改造与复用
旧代码适配新架构:
在对旧系统进行升级改造时,可能会引入新的架构或者设计模式。如果旧系统中的一些功能模块的接口不符合新架构的要求,就可以通过适配器模式来进行适配。例如,旧系统中有一个用户认证模块,它的接口是直接返回用户的基本信息字符串,而新架构要求通过一个对象来传递用户信息,包括用户名、密码、权限等多个属性。可以创建一个适配器类,将旧接口返回的字符串解析并封装成新架构所需要的用户信息对象,从而使旧的用户认证模块能够在新架构中继续使用。
硬件设备接口适配:在工业控制或者智能家居系统中,可能会存在新的控制系统与旧的硬件设备接口不匹配的情况。例如,新的智能控制系统期望通过网络协议接收和发送设备状态信息,而旧的传感器设备只有简单的串口通信接口并且数据格式不同。通过适配器模式,可以创建一个设备接口适配器,将串口通信的数据格式转换为网络协议的数据格式,并且处理通信方式的差异,使得旧设备能够在新的智能控制系统中正常工作。
软件模块交互与协作
不同模块间接口转换:在一个大型软件系统中,不同的模块可能是由不同的团队开发的,它们的接口设计可能没有考虑到相互之间的协作。当需要将这些模块集成在一起时,就可能会出现接口不兼容的问题。例如,一个数据处理模块输出的数据结构是链表形式,而另一个数据分析模块期望的数据结构是数组形式。可以通过适配器模式,创建一个数据结构适配器,将链表数据转换为数组数据,使得两个模块能够顺利地进行数据交互。
跨平台开发中的适配:
在跨平台应用开发中,不同的操作系统(如 Windows、Linux、macOS)提供的 API 接口有所不同。例如,在 Windows 系统中,创建窗口的函数是CreateWindow(),而在 Linux 系统下可能是通过XCreateWindow()函数(基于 X Window 系统)。为了使应用程序能够在不同的操作系统上运行,可以使用适配器模式。创建一个窗口创建适配器类,在不同的操作系统下实现不同的窗口创建函数,但是对外提供统一的窗口创建接口,这样就可以在跨平台开发中方便地处理操作系统接口的差异。