第41章:设计模式面试题6:实现一个适配器模式,适配第三方库接口
适配器模式,说白了就是「让不兼容的东西能一起工作」。
我面试的时候特别喜欢问这个模式。为什么?因为它太实用了。你想想看,哪个项目里没集成过第三方库?哪个第三方库的接口能完全符合你的预期?
适配器模式就是干这个活的——它把第三方库的接口,转换成你系统里期望的接口。
一个真实的场景
我记得几年前做一个支付系统。公司决定接入一个新的支付渠道,对方提供了SDK。但问题来了——我们系统内部定义了一套统一的支付接口,而第三方SDK的方法签名、参数顺序、返回值格式,全都不一样。
改我们自己的接口?不现实,因为已经对接了好几个渠道。改第三方SDK?更不可能。那怎么办?
适配器模式就是标准答案。
适配器模式的核心结构
适配器模式其实就三个角色:
- 目标接口(Target):你系统里期望的接口
- 适配者(Adaptee):第三方库的接口
- 适配器(Adapter):中间的转换层
我习惯用类适配器和对象适配器两种方式。类适配器用多重继承,对象适配器用组合。C++里我推荐对象适配器,更灵活,耦合更低。
核心原则:适配器不改变原有接口,只做转换。别想着去改第三方库的代码,那是给自己挖坑。
代码示例:对象适配器
假设我们系统里定义了一个支付接口:
// 目标接口:我们系统期望的支付方式
class IPayment {
public:
virtual ~IPayment() = default;
virtual bool pay(const std::string& orderId, double amount) = 0;
virtual std::string getStatus(const std::string& orderId) = 0;
};
现在来了一个第三方支付SDK,它的接口长这样:
// 第三方支付SDK(适配者)
class ThirdPartyPayment {
public:
// 注意:参数顺序不同,返回类型也不同
int submitPayment(double amount, const std::string& transactionId) {
// 实际调用第三方API...
return 200; // 返回HTTP状态码
}
std::string queryResult(const std::string& transactionId) {
// 返回"success"或"failed"
return "success";
}
};
你看,参数顺序反了,返回类型也不一样。直接替换?不行。写适配器:
// 适配器:把第三方接口转换成我们系统的接口
class PaymentAdapter : public IPayment {
private:
ThirdPartyPayment* m_thirdParty; // 组合方式持有第三方对象
public:
PaymentAdapter(ThirdPartyPayment* thirdParty)
: m_thirdParty(thirdParty) {}
bool pay(const std::string& orderId, double amount) override {
// 转换:调整参数顺序,转换返回值
int resultCode = m_thirdParty->submitPayment(amount, orderId);
return resultCode == 200;
}
std::string getStatus(const std::string& orderId) override {
std::string rawStatus = m_thirdParty->queryResult(orderId);
// 转换:把第三方返回的字符串映射成我们系统定义的枚举或状态码
if (rawStatus == "success") {
return "PAID";
}
return "FAILED";
}
};
使用起来就很简单了:
void processOrder(IPayment* payment, const std::string& orderId, double amount) {
if (payment->pay(orderId, amount)) {
std::cout << "支付成功" << std::endl;
}
}
int main() {
ThirdPartyPayment thirdParty;
PaymentAdapter adapter(&thirdParty);
processOrder(&adapter, "ORDER_001", 99.99);
return 0;
}
适配器模式的核心逻辑图
避坑指南
我曾经在一个项目里踩过坑。当时接手一个老系统,里面直接调用了第三方库的API,散落在各个业务代码里。后来第三方库升级,接口变了,整个系统改得鸡飞狗跳。
如果一开始就用适配器模式,只需要改适配器这一个地方就行。这就是适配器模式最大的价值——隔离变化。
注意:适配器模式不是万能的。如果第三方库的接口变化太频繁,或者语义差异太大,适配器会变得很臃肿。这时候可以考虑用门面模式(Facade)做更高层次的封装。
类适配器 vs 对象适配器
| 对比维度 | 类适配器(多重继承) | 对象适配器(组合) |
|---|---|---|
| 实现方式 | 同时继承Target和Adaptee | 继承Target,持有Adaptee对象 |
| 灵活性 | 较低,编译期绑定 | 较高,运行时可以切换Adaptee |
| 耦合度 | 高,直接依赖具体类 | 低,依赖接口或抽象类 |
| 适用场景 | Adaptee接口固定,且不需要额外适配 | 大多数实际项目 |
我个人几乎只用对象适配器。为什么?因为C++的多重继承容易带来歧义,而且组合比继承更灵活。你想想看,如果第三方库换了,我只需要换一个Adaptee对象,适配器代码基本不用动。
面试官会怎么问
面试时我一般会这样追问:
- 「如果第三方库有多个版本,接口不同,你怎么处理?」——可以用策略模式+适配器,每个版本写一个适配器,运行时选择。
- 「适配器模式和代理模式有什么区别?」——适配器侧重接口转换,代理侧重控制访问。适配器改变接口,代理不改变接口。
- 「适配器模式会影响性能吗?」——理论上多了一层调用,但现代编译器优化后几乎无感。除非在极高频调用的场景,否则不用担心。
我的建议:在项目初期就定义好接口抽象层。哪怕一开始只对接一个第三方库,也先写适配器。这个习惯帮我省了无数重构的时间。
适配器模式其实不难,难的是什么时候用、怎么用得优雅。记住一句话:不要让你的业务代码直接依赖第三方库。中间加一层适配器,未来你会感谢自己的。
公众号:蓝海资料掘金营,微信deep3321