适配器模式是一种结构型设计模式,它允许不兼容的接口之间进行协作。适配器模式就像现实世界中的电源适配器一样,可以让不同规格的插头互相兼容。
基本概念
适配器模式主要解决接口不兼容的问题,它通过将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。
三种角色
-
目标接口(Target):客户期望的接口
-
适配者(Adaptee):需要被适配的现有接口
-
适配器(Adapter):将Adaptee适配成Target接口的类
两种实现方式
1. 类适配器(通过继承实现)
#include <iostream>
// 目标接口(客户期望的接口)
class Target {
public:
virtual ~Target() = default;
virtual void request() const {
std::cout << "Target: 标准请求\n";
}
};
// 需要适配的类(现有接口)
class Adaptee {
public:
void specificRequest() const {
std::cout << "Adaptee: 特殊请求\n";
}
};
// 类适配器(通过继承Adaptee)
class Adapter : public Target, private Adaptee {
public:
void request() const override {
// 调用Adaptee的方法
specificRequest();
}
};
// 客户端代码
void clientCode(const Target* target) {
target->request();
}
int main() {
std::cout << "使用标准目标对象:\n";
Target* target = new Target;
clientCode(target);
std::cout << "\n";
std::cout << "使用适配器:\n";
Adapter* adapter = new Adapter;
clientCode(adapter);
std::cout << "\n";
delete target;
delete adapter;
return 0;
}
2. 对象适配器(通过组合实现)
#include <iostream>
#include <memory>
// 目标接口(客户期望的接口)
class Target {
public:
virtual ~Target() = default;
virtual void request() const {
std::cout << "Target: 标准请求\n";
}
};
// 需要适配的类(现有接口)
class Adaptee {
public:
void specificRequest() const {
std::cout << "Adaptee: 特殊请求\n";
}
};
// 对象适配器(通过组合Adaptee)
class Adapter : public Target {
private:
std::unique_ptr<Adaptee> adaptee_;
public:
Adapter(Adaptee* adaptee) : adaptee_(adaptee) {}
void request() const override {
// 调用Adaptee的方法
adaptee_->specificRequest();
}
};
// 客户端代码
void clientCode(const Target* target) {
target->request();
}
int main() {
std::cout << "使用标准目标对象:\n";
Target* target = new Target;
clientCode(target);
std::cout << "\n";
std::cout << "使用适配器:\n";
Adapter* adapter = new Adapter(new Adaptee);
clientCode(adapter);
std::cout << "\n";
delete target;
delete adapter;
return 0;
}
适配器模式的应用场景
-
遗留系统集成:当需要使用现有的类,但其接口与你的代码不兼容时
-
第三方库适配:当需要使用第三方库但接口不符合你的需求时
-
接口版本兼容:不同版本的接口需要一起工作时
-
统一接口:多个类有相似功能但接口不同,需要统一接口时
适配器模式的优缺点
优点
-
单一职责原则:可以将接口转换代码与业务逻辑分离
-
开闭原则:可以在不修改现有代码的情况下引入新的适配器
-
提高了类的复用性
-
增加了类的透明度
缺点
-
增加了系统的复杂性
-
有时需要牺牲一些性能(因为多了一层调用)
C++中的实际应用示例
STL中的适配器
C++标准模板库(STL)中有许多适配器的例子:
-
容器适配器:
-
stack
适配了deque
或list
-
queue
适配了deque
或list
-
priority_queue
适配了vector
或deque
-
#include <iostream>
#include <stack>
#include <vector>
int main() {
// stack是适配器,默认使用deque作为底层容器
std::stack<int> s1;
// 也可以指定底层容器为vector
std::stack<int, std::vector<int>> s2;
s1.push(1);
s1.push(2);
s1.push(3);
while (!s1.empty()) {
std::cout << s1.top() << " ";
s1.pop();
}
return 0;
}
-
迭代器适配器:
-
reverse_iterator
-
back_insert_iterator
-
front_insert_iterator
-
函数对象适配器
C++11之前的标准库中有bind1st
、bind2nd
等函数适配器:
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// 使用bind2nd适配器将less转换为判断小于3的函数
auto it = std::find_if(v.begin(), v.end(),
std::bind2nd(std::less<int>(), 3));
if (it != v.end()) {
std::cout << "First element less than 3: " << *it << std::endl;
}
return 0;
}
在现代C++中,可以使用lambda表达式替代这些适配器:
auto it = std::find_if(v.begin(), v.end(), [](int x) { return x < 3; });
适配器模式与其它模式的关系
-
桥接模式:通常在设计初期使用,而适配器模式通常在已有系统中使用
-
装饰器模式:不改变接口但增强了功能,适配器则改变接口
-
代理模式:保持接口不变,适配器则改变接口
-
外观模式:为一组接口提供简化接口,适配器则尝试使单个接口可用
总结
适配器模式是C++中非常有用的设计模式,特别是在集成现有代码或第三方库时。通过适配器,可以让不兼容的接口协同工作,而不需要修改原有代码。C++中的类适配器和对象适配器提供了两种不同的实现方式,各有优缺点。STL中的许多组件也使用了适配器模式的思想,这证明了它在实际开发中的实用性。
在实际开发中,应当根据具体情况选择使用适配器模式,权衡其带来的灵活性和增加的复杂性。
生活中的"万能转换插头"
生活比喻:国际旅行中的电源适配器
想象你要出国旅行,带了手机、电脑等电子设备,但到了目的地发现:
-
你的设备插头是两脚扁型(中国标准)
-
酒店的插座是三脚圆型(英国标准)
这时候你该怎么办?直接硬插肯定不行,你有三个选择:
-
改造设备:把设备拆开改造成英国插头(风险高,不现实)
-
改造酒店:让酒店换插座(几乎不可能)
-
使用转换插头:买一个适配器,完美解决问题
这个转换插头就是适配器模式的完美体现!
编程世界的适配器
在软件开发中同样存在这种"接口不匹配"的情况:
-
老系统是用C语言写的,新系统用C++
-
第三方库的接口和你的系统不兼容
-
不同团队开发的模块接口标准不一致
适配器模式就是软件界的"转换插头",让你无需修改现有代码就能让不兼容的接口协同工作。
通俗总结
适配器模式的本质就是:当你想用A插头插B插座时,不是去改造A或B,而是找一个转换器让它们能连上。
它的三大特点:
-
兼容性:让新旧/不同标准的接口能一起工作
-
非侵入性:不修改原有代码
-
灵活性:可以随时增加新的适配器支持更多接口
现实中的其他例子
-
读卡器:把SD卡转换成USB接口
-
翻译官:在中英会谈中充当"语言适配器"
-
转接线:把Type-C接口转换成3.5mm耳机孔
-
货币兑换:把美元"适配"成人民币使用
什么时候该用适配器模式?
当系统出现以下情况时:
-
想用现成的类,但接口不匹配
-
需要集成多个不兼容的子系统
-
想创建一个可复用的类来与未知类协作
-
需要使用第三方库但接口不符合需求
记住适配器模式的口诀:不匹配,加适配;不改源码,不伤和气!