适配器模式及优化
适配器模式(Adapter Pattern)是一种结构型设计模式,它能使接口不兼容的类可以相互合作。核心思想是通过引入一个适配器类,通过转换接口而非修改实现,将一个类的接口转换成客户端期望的另一个接口,从而解决因接口不匹配而无法直接使用的问题。
一、介绍
核心角色
- 目标接口(Target):客户端期望的接口,定义了客户端可以使用的方法。
- 适配者(Adaptee):需要被适配的现有接口,其方法与目标接口不兼容。
- 适配器(Adapter):实现目标接口,并内部包含适配者的实例,通过转换调用适配者的方法。
适配器模式的适用场景
- 集成现有组件
当需要使用一个已存在的类,但它的接口与系统要求的接口不匹配时(如示例中的旧设备接口)。 - 复用遗留代码
维护旧系统时,需要将新功能与遗留代码集成,而遗留代码的接口无法修改(如第三方库、老旧模块)。 - 跨平台兼容
不同平台提供的功能相同但接口不同(如Windows和Linux的文件操作API),通过适配器统一接口。 - 测试与模拟
单元测试中,需要用模拟对象替代真实对象,但模拟对象的接口与被替代对象不一致时。
优点
- 接口兼容性:解决了不同接口之间的冲突,使原本无法协作的类可以一起工作。
- 代码复用:无需修改现有类(尤其是第三方库或遗留代码),通过适配实现复用。
- 松耦合设计:客户端只依赖目标接口,与适配者类解耦,提高系统灵活性。
- 透明性:客户端无需知道适配者的存在,使用方式与目标接口一致(如示例中对新旧设备的调用方式相同)。
二、实现
设备数据格式适配,假设系统中存在两种设备:
- 旧设备(
LegacySensor
):输出数据格式为(温度, 湿度)
的字符串(如"25.5,60.0"
)。 - 新设备(
ModernSensor
):输出数据格式为结构化的SensorData
(包含温度、湿度成员变量)。
客户端代码希望统一使用getSensorData()
方法获取SensorData
结构,因此需要为旧设备创建适配器,使其接口与新设备一致。
// sensor_adapter.h
#include <string>
#include <sstream>
#include <iostream>// 目标接口:客户端期望的数据格式和方法
struct SensorData {float temperature; // 温度(℃)float humidity; // 湿度(%)
};class SensorTarget {
public:virtual ~SensorTarget() = default;virtual SensorData getSensorData() = 0; // 客户端统一调用的方法
};// 适配者:旧设备(接口不兼容,输出字符串)
class LegacySensor {
public:// 旧设备的方法:返回"温度,湿度"格式的字符串std::string fetchData() {// 模拟硬件读取(实际中可能是从硬件寄存器读取)return "25.5,60.0";}
};// 适配器:将旧设备接口转换为目标接口
class LegacySensorAdapter : public SensorTarget {
private:LegacySensor* legacySensor; // 包含适配者实例// 辅助方法:解析字符串为SensorDataSensorData parseData(const std::string& data) {SensorData result;std::stringstream ss(data);std::string tempStr, humStr;// 分割字符串(格式:"温度,湿度")std::getline(ss, tempStr, ',');std::getline(ss, humStr, ',');result.temperature = std::stof(tempStr);result.humidity = std::stof(humStr);return result;}public:explicit LegacySensorAdapter(LegacySensor* sensor) : legacySensor(sensor) {}// 实现目标接口:调用旧设备方法并转换格式SensorData getSensorData() override {std::string rawData = legacySensor->fetchData(); // 调用适配者方法return parseData(rawData); // 转换格式并返回}
};// 新设备:直接实现目标接口(无需适配)
class ModernSensor : public SensorTarget {
public:SensorData getSensorData() override {// 模拟硬件读取(直接返回结构化数据)return {26.3, 58.5};}
};// 客户端代码:只依赖目标接口,不关心具体设备类型
void printSensorData(SensorTarget* sensor) {SensorData data = sensor->getSensorData();std::cout << "温度:" << data.temperature << "℃,湿度:" << data.humidity << "%" << std::endl;
}int main() {// 1. 使用新设备(直接实现目标接口)ModernSensor modernSensor;std::cout << "=== 新设备数据 ===" << std::endl;printSensorData(&modernSensor);// 2. 使用旧设备(通过适配器转换接口)LegacySensor legacySensor;LegacySensorAdapter adapter(&legacySensor); // 适配旧设备std::cout << "\n=== 旧设备数据(通过适配器) ===" << std::endl;printSensorData(&adapter); // 客户端调用方式与新设备完全一致return 0;
}
输出结果
=== 新设备数据 ===
温度:26.3℃,湿度:58.5%=== 旧设备数据(通过适配器) ===
温度:25.5℃,湿度:60%
应用场景
- 数据格式转换
- 不同数据源返回格式适配(如JSON与XML格式转换)。
- 旧系统的字符串数据与新系统的结构化数据适配(如示例)。
- 库/框架集成
- 集成第三方库时,通过适配器封装库的接口,使调用方式与系统一致(如将Boost库的接口适配为自定义接口)。
- 不同日志库(如log4cpp、spdlog)的接口统一。
- 硬件接口适配
- 不同传感器的输出接口适配(如示例中的新旧传感器)。
- 不同型号打印机的驱动接口统一。
- 系统迁移
- 逐步替换旧系统:先用适配器使新旧系统共存,再逐步迁移功能。
- 从单体系统到微服务的过渡:用适配器封装微服务接口,使旧系统无缝调用。
二、优化
优化点
- 支持多适配者适配:一个适配器可同时适配多个接口不兼容的类,统一转换为目标接口。
- 双向适配器:不仅能将适配者接口转换为目标接口,还能将目标接口转换为适配者接口,支持双向交互。
- 模板化适配:通过模板减少适配器类的重复定义,简化适配逻辑。
- 动态适配:运行时动态选择适配策略,无需提前确定适配关系。
- 适配链:多个适配器组合形成适配链,处理多层接口转换(如A→B→C)。
// sensor_adapter.h
#include <string>
#include <sstream>
#include <iostream>
#include <memory>
#include <vector>// 目标接口1:客户端期望的结构化数据接口
struct SensorData {float temperature; // 温度(℃)float humidity; // 湿度(%)float pressure; // 气压(kPa)- 扩展字段// 便于打印void print() const {std::cout << "温度:" << temperature << "℃,"<< "湿度:" << humidity << "%"<< ",气压:" << pressure << "kPa" << std::endl;}
};class SensorTarget {
public:virtual ~SensorTarget() = default;virtual SensorData getSensorData() = 0; // 获取结构化数据virtual void setCalibration(float factor) {} // 校准(默认空实现)
};// 目标接口2:旧系统期望的字符串接口(用于双向适配)
class LegacyStringTarget {
public:virtual ~LegacyStringTarget() = default;virtual std::string getRawString() = 0; // 获取原始字符串
};// ------------------------------
// 适配者1:基础旧传感器(仅温度+湿度,字符串输出)
// ------------------------------
class BasicLegacySensor {
public:std::string readBasic() {return "25.5,60.0"; // 格式:温度,湿度}void setOldCalibration(int level) {std::cout << "旧传感器校准等级设置为:" << level << std::endl;}
};// ------------------------------
// 适配者2:高级旧传感器(温度+湿度+气压,分号分隔)
// ------------------------------
class AdvancedLegacySensor {
public:std::string readAdvanced() {return "26.3;58.5;101.3"; // 格式:温度;湿度;气压}
};// ------------------------------
// 适配者3:第三方气象站(函数式接口)
// ------------------------------
namespace ThirdParty {void getWeatherData(float& temp, float& hum, float& press) {temp = 24.8;hum = 62.0;press = 100.9; // 直接通过引用返回数据}
}// ------------------------------
// 模板适配器:简化单一适配者的适配逻辑
// ------------------------------
template <typename Adaptee, typename GetterFunc>
class TemplateSensorAdapter : public SensorTarget {
private:Adaptee* adaptee;GetterFunc getter; // 获取数据的函数对象std::function<SensorData(const std::string&)> parser; // 解析函数public:TemplateSensorAdapter(Adaptee* a, GetterFunc g, decltype(parser) p): adaptee(a), getter(std::move(g)), parser(std::move(p)) {}SensorData getSensorData() override {return parser(getter(adaptee)); // 调用适配者方法并解析}
};// ------------------------------
// 多适配者适配器:同时适配多个旧设备
// ------------------------------
class MultiSensorAdapter : public SensorTarget {
private:std::vector<SensorTarget*> adapters; // 包含多个子适配器public:void addAdapter(SensorTarget* adapter) {if (adapter) adapters.push_back(adapter);}// 示例:返回所有设备的平均值SensorData getSensorData() override {if (adapters.empty()) return {0, 0, 0};SensorData avg{};for (auto adapter : adapters) {auto data = adapter->getSensorData();avg.temperature += data.temperature;avg.humidity += data.humidity;avg.pressure += data.pressure;}int count = adapters.size();avg.temperature /= count;avg.humidity /= count;avg.pressure /= count;return avg;}
};// ------------------------------
// 双向适配器:同时适配新旧接口
// ------------------------------
class BidirectionalAdapter : public SensorTarget, public LegacyStringTarget {
private:BasicLegacySensor* legacySensor;// 字符串转SensorDataSensorData strToData(const std::string& s) {SensorData data{};std::stringstream ss(s);std::string temp, hum;std::getline(ss, temp, ','), std::getline(ss, hum, ',');data.temperature = std::stof(temp);data.humidity = std::stof(hum);data.pressure = 0; // 旧设备无气压数据return data;}// SensorData转字符串std::string dataToStr(const SensorData& d) {return std::to_string(d.temperature) + "," + std::to_string(d.humidity);}public:explicit BidirectionalAdapter(BasicLegacySensor* s) : legacySensor(s) {}// 实现SensorTarget接口(旧→新)SensorData getSensorData() override {return strToData(legacySensor->readBasic());}// 实现LegacyStringTarget接口(新→旧)std::string getRawString() override {// 模拟:将新接口的结构化数据转为旧接口的字符串格式return dataToStr(getSensorData());}// 适配校准方法(新接口→旧接口)void setCalibration(float factor) override {// 将新接口的浮点校准系数转换为旧接口的整数等级legacySensor->setOldCalibration(static_cast<int>(factor * 10));}
};// ------------------------------
// 新设备:直接实现目标接口
// ------------------------------
class ModernSensor : public SensorTarget {
public:SensorData getSensorData() override {return {27.1, 56.8, 101.5}; // 温度,湿度,气压}void setCalibration(float factor) override {std::cout << "新传感器校准系数设置为:" << factor << std::endl;}
};// 客户端函数1:使用新接口处理数据
void processNewSensor(SensorTarget* sensor) {std::cout << "处理新接口数据:";sensor->getSensorData().print();
}// 客户端函数2:使用旧接口处理数据(用于双向适配测试)
void processLegacySensor(LegacyStringTarget* sensor) {std::cout << "处理旧接口数据:" << sensor->getRawString() << std::endl;
}int main() {// 1. 模板适配器:适配高级旧传感器(简化适配代码)AdvancedLegacySensor advSensor;auto advAdapter = TemplateSensorAdapter(&advSensor,[](AdvancedLegacySensor* s) { return s->readAdvanced(); }, // 获取数据[](const std::string& s) { // 解析数据(温度;湿度;气压)SensorData data{};std::stringstream ss(s);std::string temp, hum, press;std::getline(ss, temp, ';'), std::getline(ss, hum, ';'), std::getline(ss, press, ';');data.temperature = std::stof(temp);data.humidity = std::stof(hum);data.pressure = std::stof(press);return data;});std::cout << "=== 模板适配器(高级旧传感器) ===" << std::endl;processNewSensor(&advAdapter);// 2. 模板适配器:适配第三方气象站(函数式接口)auto thirdPartyAdapter = TemplateSensorAdapter(nullptr, // 无实例,直接调用命名空间函数[](void*) { float t, h, p;ThirdParty::getWeatherData(t, h, p);return std::to_string(t) + "," + std::to_string(h) + "," + std::to_string(p);},[](const std::string& s) { // 解析第三方数据SensorData data{};std::stringstream ss(s);std::string temp, hum, press;std::getline(ss, temp, ','), std::getline(ss, hum, ','), std::getline(ss, press, ',');data.temperature = std::stof(temp);data.humidity = std::stof(hum);data.pressure = std::stof(press);return data;});std::cout << "\n=== 模板适配器(第三方气象站) ===" << std::endl;processNewSensor(&thirdPartyAdapter);// 3. 多适配者适配器:整合多个设备数据(取平均值)MultiSensorAdapter multiAdapter;BasicLegacySensor basicSensor;BidirectionalAdapter bidirAdapter(&basicSensor);ModernSensor modernSensor;multiAdapter.addAdapter(&advAdapter);multiAdapter.addAdapter(&thirdPartyAdapter);multiAdapter.addAdapter(&bidirAdapter);multiAdapter.addAdapter(&modernSensor);std::cout << "\n=== 多设备适配器(平均值) ===" << std::endl;processNewSensor(&multiAdapter);// 4. 双向适配器:同时支持新旧接口std::cout << "\n=== 双向适配器测试 ===" << std::endl;processNewSensor(&bidirAdapter); // 用新接口调用processLegacySensor(&bidirAdapter); // 用旧接口调用bidirAdapter.setCalibration(0.8f); // 适配校准方法return 0;
}
输出结果
=== 模板适配器(高级旧传感器) ===
处理新接口数据:温度:26.3℃,湿度:58.5%,气压:101.3kPa=== 模板适配器(第三方气象站) ===
处理新接口数据:温度:24.8℃,湿度:62%,气压:100.9kPa=== 多设备适配器(平均值) ===
处理新接口数据:温度:25.85℃,湿度:59.375%,气压:100.925kPa=== 双向适配器测试 ===
处理新接口数据:温度:25.5℃,湿度:60%,气压:0kPa
处理旧接口数据:25.5,60.0
旧传感器校准等级设置为:8
优化点说明
-
模板化适配(减少重复代码)
通过TemplateSensorAdapter
模板类,将适配逻辑(获取数据+解析数据)通过函数对象注入,避免为每个适配者编写单独的适配器类。例如:- 适配
AdvancedLegacySensor
时,只需传入获取数据的lambda和解析字符串的lambda。 - 适配第三方库的函数式接口(
ThirdParty::getWeatherData
)时,无需修改原函数,直接通过模板适配。
- 适配
-
多适配者适配(整合多源数据)
MultiSensorAdapter
可聚合多个适配器,统一对外提供目标接口。示例中计算多个设备数据的平均值,适合需要整合多源异构数据的场景(如气象站网络、分布式传感器系统)。 -
双向适配(支持新旧系统互操作)
BidirectionalAdapter
同时实现SensorTarget
(新接口)和LegacyStringTarget
(旧接口),既可以将旧设备数据转换为新格式,也可以将新格式数据转换为旧格式,解决了新旧系统双向交互的问题。例如:- 新系统可通过
getSensorData()
获取旧设备数据。 - 旧系统可通过
getRawString()
获取新格式转换后的旧格式数据。 - 校准方法
setCalibration
也被适配(浮点系数→整数等级)。
- 新系统可通过
-
接口扩展与兼容性
目标接口SensorData
扩展了气压字段,适配器通过合理默认值(如旧设备气压设为0)保持兼容性,无需修改旧设备代码。 -
灵活性与扩展性
新增适配者时,无需修改现有适配器框架,只需:- 对简单适配场景,使用
TemplateSensorAdapter
传入获取和解析逻辑。 - 对复杂场景,继承
SensorTarget
实现自定义适配器。
- 对简单适配场景,使用
适用场景扩展
- 企业系统集成:整合多个部门的异构系统(如ERP、CRM),通过多适配者适配器统一数据格式。
- API版本兼容:为不同版本的API提供双向适配器,既支持旧版本调用新版本,也支持新版本兼容旧版本。
- 跨语言交互:通过适配器转换C++与Python/Java的接口(如用SWIG生成的接口适配自定义接口)。
- 设备网关:工业物联网中,适配不同协议的传感器(如Modbus、MQTT),统一数据采集接口。