适配器模式是解决接口不匹配的胶水层,用于不修改旧代码、不重写新逻辑时连接双方;必须使用场景包括第三方库、遗留模块与新dto不兼容、异步接口类型不一致等;核心标准是一方接口不可变且调用契约已定死。

适配器模式不是“加功能”,而是“接上就能用”——它解决的是已有代码和新需求之间接口对不上时,不改旧代码、不重写新逻辑,靠一个薄层把两边连通的问题。
什么时候必须写 Adapter 而不是直接调用?
当你看到这类信号,就该停手、建适配器,而不是硬塞 if-else 或强行改造原类:
- 第三方库的类(比如
ThirdPartyPayment)你没法改源码,但它方法名是make_payment(amount, currency),而你的业务层只认pay(amount_in_yuan) - 遗留模块返回的是
Map<string object></string>,但新 service 需要UserInfoDTO,且 DTO 字段命名/类型/嵌套结构都不同 - 测试中想 mock 一个外部 HTTP 客户端,但它的接口是泛型回调式(
executeAsync(Request, Consumer<response>)</response>),而你统一用的是CompletableFuture<response></response>
核心判断标准:一方接口固定不可动,另一方调用契约已定死,中间没有“商量余地”。这时 Adapter 就是唯一的胶水。
对象适配器比类适配器更安全,但别盲目组合
Java/C# 中优先选对象适配器(组合),不是因为它“高级”,而是因为:
- 类适配器依赖多重继承(如
class MediaAdapter extends AdvancedMediaPlayer implements MediaPlayer),Java 不支持,C# 也不允许多继承 class - 组合能复用同一个
Adaptee实例给多个Adapter,比如一个VlcPlayer可同时被音频播放器和视频转码服务复用 - 但注意:如果
Adaptee构造开销大(比如初始化连接池、加载配置文件),而你每次 newAdapter都 new 一次Adaptee,就会造成资源浪费
实操建议:Adapter 的构造参数应明确接收已创建好的 Adaptee 实例,而非在内部 new —— 这既是解耦,也是控制生命周期。
Adapter 里最容易漏掉的三件事
写完编译通过、跑通一个 case 就提交?大概率埋雷:
-
空值没兜底:被适配方法可能返回
null,而目标接口契约要求非空(比如String getId()),Adapter 必须主动判空抛IllegalArgumentException或返回默认值,不能让空指针穿透过去 -
异常没转换:Adaptee 抛的是
IOException,Target 接口声明 throwsBusinessException,Adapter 必须 catch 并 rethrow,否则编译失败或运行时崩 -
线程不安全没声明:如果 Adaptee 是无状态的(如纯函数工具类),Adapter 可安全共享;但如果它内部持有了
SimpleDateFormat或缓存 map,Adapter 就得加同步或注明“非线程安全”,否则高并发下数据错乱
这些不是“可选优化”,是 Adapter 合格线——它对外代表的是 Target 接口,所有契约责任,都由它承担。
真正难的从来不是写一个 Adapter 类,而是判断哪一层该切、哪一层不该切;以及当 Adaptee 自身也开始演化(比如第三方库升级后加了新方法),Adapter 是否该跟着变,还是该用装饰器再包一层。边界模糊时,先看调用方是否感知变化——不感知,就是 Adapter 该做的事。










