适配器模式(Adapter):把不兼容的接口翻译成客户端要的
更新时间:2026-08-25。本文按"先看不用模式的困境 → 再用模式重构 → 类图对比分析"的结构,论述适配器模式。可运行代码:demos/design-patterns/adapter。
一、问题场景:接口不匹配,但两边都不能改
第三方日志库(log(info, fmt, args))与自家日志接口(Log::Info(msg))不匹配;老模块输出 int 温度,新模块要 double;新系统接老系统协议。改造任何一边成本都很高(第三方库不能改、老系统冻结),只能加一层翻译。
二、不使用模式:客户端散落转换逻辑
cpp
// 客户端每次调用都自己做格式转换
logger.Info(std::format("temp={}", third_party::log_to_string(reading)));
// 换了日志库:所有调用点重新改| 缺陷 | 说明 |
|---|---|
| 转换逻辑散落 | 每个调用点各写一份适配代码 |
| 违反 DIP | 客户端直接依赖第三方具体类/格式 |
| 换库=全量改 | 接口不统一,替换成本高 |
| 单元测试难 | 测试耦合第三方实现 |
不使用模式的类图

三、使用模式:适配器把第三方接口翻译成目标接口
适配器模式定义一个客户端期望的目标接口,适配器类实现该接口并内部委托给第三方(被适配者),翻译工作全部收进适配器。
cpp
// logger_adapter.h
class ILogger { // 目标接口(客户端期望的)
public:
virtual void Info(const std::string& msg) = 0;
virtual ~ILogger() = default;
};
class ThirdPartyLoggerAdapter : public ILogger { // 适配器
public:
explicit ThirdPartyLoggerAdapter(ThirdPartyLogger& raw) : raw_(raw) {}
void Info(const std::string& msg) override {
raw_.log(LOG_INFO, "%s", msg.c_str()); // 翻译:自家调用 → 第三方 API
}
private:
ThirdPartyLogger& raw_; // 被适配者(组合,不改第三方)
};
// 客户端:只依赖 ILogger
void client(ILogger& log) { log.Info("hello"); }使用模式的类图

四、类图对比与分析
| 维度 | 不使用(散落转换) | 使用适配器 |
|---|---|---|
| 转换逻辑 | 散落各调用点 | 收敛到适配器一处 |
| 客户端依赖 | 第三方具体类/格式 | 目标抽象接口 |
| 换第三方库 | 全量修改 | 重写适配器内部,调用方零改动 |
| 测试 | 耦合第三方 | mock 目标接口即可 |
| 结构成本 | 无 | 多一个适配器类 |
分析结论:适配器把"翻译"集中到唯一的边界类,让客户端永远面向自己的语言。它是"事后补救型"模式——当系统边界处接口不一致且双方都不能改时,用适配器隔离变化。类适配器(多继承,需能改第三方代码/多继承支持)与对象适配器(组合委托,推荐)两种形态,C++ 优先对象适配器。
五、适用边界
| 适用 | 不适用 |
|---|---|
| 第三方库/老系统接口与自家不一致 | 可以修改被适配者(直接改接口更好) |
| 换库预期存在 | 接口天然一致(适配器是冗余) |
| 遗留系统集成 | 需要大量互转的网状适配(考虑 Facade 先行) |
与外观模式的区别:适配器是"翻译"(把 A 接口变成 B 接口,一对一语义对齐);外观是"简化"(把一整套子系统变成简单门面,一对多聚合)。适配器服务现有接口,外观定义新的简化接口。
一句话总结
适配器模式用组合委托在客户端与不兼容的第三方之间加一层"翻译器",转换逻辑全部收敛到适配器内——客户端只依赖自己的目标接口,换第三方库只动适配器内部;它是接口不一致且双方不可改时的隔离墙。