设计模式实战篇(七):适配器模式 —— 让“不兼容的接口”优雅合作的万能转换器
在真实工程里,适配器几乎无处不在:框架升级、第三方 SDK 接入、系统重构、旧系统兼容、新旧协议过渡……
适配器模式就像“接口世界的翻译官”,让原本不兼容的对象继续合作而无需修改源代码。
🔌 一、为什么需要适配器?(从真实工程痛点开始)
在软件工程中,你一定遇到过这些情况:
场景 1:旧系统接口无法直接迁移到新系统
旧方法签名为:
User getUser(String userId);
但新系统要求:
UserVo queryUser(QueryRequest request);
你不能改旧代码,却要兼容新接口。
场景 2:接入第三方 SDK,但对方接口与你系统完全不同
例如你的日志系统使用:
log.info(String message)
而新引入的云监控需要:
send(LogData data)
场景 3:API 协议升级后,不想引发全系统改动
例如多个模块还在调用旧方法,而你希望:
- 不修改老代码
- 新功能走新接口
- 二者共存
🔥 这些问题的共同点是:
❌ 必须兼容旧结构,但不能修改旧代码
❌ 两个接口不一样,却必须能一起工作
这就是适配器模式的用武之地。
🧭 二、适配器模式核心
适配器 ≈ 转换插头
让使用者不用关心“电压是否一致”,你只提供一个 能把 A 变成 B 的中间层。
简单来说:
调用旧接口的代码不变 → 适配器负责把调用转换成新接口格式。
🧱 三、适配器的三种形态
适配器模式有 3 种常见用法:
1)类适配器(继承实现)
通过 extends:
LegacyService → Adapter → NewService
适合:必须复用父类逻辑、单继承足够时
缺点:受限于继承
2)对象适配器(组合方式,最常用)
结构:
Adapter 里面持有 NewService
优点:
- 灵活
- 不受继承限制
- 可以适配多个不同的实现
这是实际项目最常见的方式。
3) 接口适配器(抽象类 + 空实现)
用于需要实现多个方法,但你只关心部分方法的场景,例如:
- MouseListener
- KeyListener
避免实现一堆你不需要的方法。
🔧 四、业务案例:支付系统迁移
假设老系统支付接口是:
public interface OldPayService {boolean pay(String uid, double amount);
}
而新架构统一支付中心的接口是:
public interface NewPayGateway {PayResult process(PayRequest request);
}
两者格式完全不同,但你现在:
- 不能改老代码
- 不能让 N 个业务线全部重写逻辑
- 只能通过适配器融合它们
🎛️ 五、实现演进:从问题到最终解法
初版:业务线硬编码转换
newPayGateway.process(new PayRequest(uid, money));
问题:
- 所有业务线都要改代码
- 耦合严重
- 出错概率极高
🚫 不符合开闭原则
第二版:写一个工具类
PayResult pay(String uid, double amount) {return newPayGateway.process(new PayRequest(uid, amount))
}
问题:
- 老接口 OldPayService 丢失
- 多模块接入仍要改
第三版:对象适配器
让适配器实现老接口,内部调用新接口:
public class PayAdapter implements OldPayService {private final NewPayGateway gateway;public PayAdapter(NewPayGateway gateway) {this.gateway = gateway;}@Overridepublic boolean pay(String uid, double amount) {PayRequest req = new PayRequest(uid, amount);PayResult res = gateway.process(req);return res.getCode() == 200;}
}
🔥 外部业务不需要改任何代码!
🧱 六、适配器的结构拆解.
调用方(旧逻辑)│▼OldPayService ←(保持不变)│▼
【适配器:PayAdapter】│ 把旧方法转成新格式▼NewPayGateway
核心过程:
- 旧接口 → 调用适配器
- 适配器 → 把参数包装成新格式
- 新系统处理
- 适配器再次转换 → 旧系统可理解的结果
❌ 七、常见误区
误区 1:适配器用多了会变成“复杂度黑洞”
过度适配=系统过多“翻译层”:
A → Adapter1 → Adapter2 → Adapter3 → B
💡 建议:
- 每条路径最多 1 个适配器
- 对老接口做“最终适配”,而不是层层递归
误区 2:适配器 ≠ 工厂模式
| 区别 | 适配器 | 工厂 |
|---|---|---|
| 目的 | 让两个不兼容的接口合作 | 负责创建对象 |
| 本质 | 转换器 | 构建器 |
| 作用时间 | 运行时 | 创建时 |
很多面试题故意混淆这一点。
误区 3:适配器不是为了“性能”,而是为了“兼容”
适配器本身会带来额外开销,例如对象转换,这很正常。
它的价值是 解耦 + 兼容,不是加速。
🧩 八、代码示例
1. 老接口
public interface OldPayService {boolean pay(String uid, double amount);
}
2. 新接口
public interface NewPayGateway {PayResult process(PayRequest request);
}
3. 适配器
public class PayAdapter implements OldPayService {private final NewPayGateway gateway;public PayAdapter(NewPayGateway gateway) {this.gateway = gateway;}@Overridepublic boolean pay(String uid, double amount) {PayRequest req = new PayRequest(uid, amount);PayResult res = gateway.process(req);return res.getCode() == 200;}
}
4. 使用方式
OldPayService payService =new PayAdapter(new NewPayGatewayImpl());payService.pay("U1001", 99.0);
业务方无感知:✔
🛰️ 九、适配器的案例
1)协议适配(Protocol Adapter)
例如你的网关接受:
- HTTP/JSON
- gRPC
- MQ
- WebSocket
- Protobuf
- 内网自定义二进制协议
但你的业务服务只认:
统一内部协议 InternalDTO
此时网关需要多个适配器:
JsonAdapter → InternalDTO
GrpcAdapter → InternalDTO
MqAdapter → InternalDTO
ProtoAdapter → InternalDTO
🌉 十、跨语言适配(Java ⇆ Python ⇆ Go)
现代企业经常有多语言服务,例如:
- Java(主线)
- Python(AI 服务)
- Go(高性能网关)
这些服务的 DTO、字段、结构往往不同。
适配器可用于:
- 字段映射
- 类型转换
- 语言间协议编码、解码
- 图像/embedding 数据统一格式化
- RPC 桥接
示例(Java 端 Adapter 适配 Python 返回的 Map):
public class PythonUserAdapter implements UserDomainObject {private final Map<String, Object> pythonMap;public PythonUserAdapter(Map<String, Object> pythonMap) {this.pythonMap = pythonMap;}@Overridepublic String getName() {return (String) pythonMap.get("username");}@Overridepublic int getAge() {return ((Number) pythonMap.get("age")).intValue();}
}
🧬 十一、架构演进中的适配器
当系统重构时:
- DTO 变了 → 写适配器
- 服务名变了 → 写适配器
- 方法签名变了 → 写适配器
- 接口调用方过多无法改 → 写适配器
适配器承担 兼容老代码 的任务,让重构:
- 不影响运行
- 可平滑迁移
- 可灰度发布
- 可随时回滚旧版本
🔭 十二、适配器与相关模式对照
| 模式 | 容易混淆原因 | 本质区别 |
|---|---|---|
| 适配器 | 两边接口不同 | 使“不兼容接口”合作 |
| 外观(Facade) | 都是“包装” | 外观是“统一多接口”,不是转换接口 |
| 代理(Proxy) | 都有“中间层” | 代理用于控制访问(缓存、权限) |
| 装饰器(Decorator) | 都是“包装” | 装饰是增强功能,不修改接口本身 |
| 桥接(Bridge) | 都是“结构型模式” | 桥接是抽象与实现分离,不做接口兼容 |
🧠 十三、进一步的工程技巧:适配器注册中心
在协议多样的大型系统中,仅仅创建适配器不够,需要:
- 可动态加载
- 可根据协议自动选择适配器
- 可对同一协议多版本适配
- 可扩展
示例:
public class AdapterRegistry {private static final Map<String, Adapter> ADAPTERS = new HashMap<>();public static void register(String key, Adapter adapter) {ADAPTERS.put(key, adapter);}public static Adapter get(String key) {return ADAPTERS.get(key);}
}
注册适配器:
AdapterRegistry.register("json", new JsonAdapter());
AdapterRegistry.register("grpc", new GrpcAdapter());
AdapterRegistry.register("mq", new MqAdapter());
调用:
Adapter adapter = AdapterRegistry.get(protocolType);
adapter.convert(request);
🔥 优点:
- 插拔式适配
- 可配置化(适合 Spring 配置)
- 扩展性极强
- 非常适合网关/中台
🎯 总结
适配器模式解决了软件工程中最痛苦的问题之一:
“接口已经不兼容了,但我们还不能重构老代码。”
它既是过渡方案,也是企业级架构的稳定器。
