Java 23 种设计模式:从踩坑到精通 | 适配器模式 ------ 让不兼容的接口也能一起工作
摘要 :对接第三方 SDK 时发现接口不兼容?接手老项目时发现 API 格式对不上?直接改源码风险大,重写又成本太高。适配器模式通过一个"转换头",让原本不兼容的接口协同工作,无需修改原有代码。本文从真实的"物流轨迹推送"场景出发,完整讲解对象适配器、类适配器、缺省适配器三种形态,深入 Spring MVC
HandlerAdapter源码,结合 JDK I/O、异步与响应式适配、泛型适配器等现代 Java 实践,帮你掌握"接口转换"的设计精髓。
📖 《Java 23 种设计模式:从踩坑到精通》开篇:系列介绍与目录 | 上一篇:原型模式 | 当前:适配器模式 | 下一篇:桥接模式
🔗 返回系列总目录
1. 从"物流轨迹对接"的水土不服说起
某天,你们公司接入了第三方的物流轨迹服务,对方提供了一个功能强大的 LogisticsTracker 类:
java
// 第三方物流 SDK,不能修改源码
public class LogisticsTracker {
public String queryTrajectory(String waybillNo, String carrierCode) {
// 查询物流轨迹,返回 JSON 字符串
return "{\"status\":\"运输中\",\"location\":\"北京分拨中心\"}";
}
}
这个方法参数是两个字符串,返回的是 JSON。但你整个 WMS 系统里统一用的是 TrajectoryService 接口------参数是一个 TrajectoryQuery 对象,返回的是 TrajectoryResult 对象。格式完全对不上。
直接改调用方代码?几十个业务模块都在用 TrajectoryService,改起来风险极大。修改第三方 SDK?不可能。放弃不用重新造轮子?更不可能。
适配器模式就是用来解决这种"接口水土不服"的:它像一个转接头,把一个类的接口转换成客户端期望的另一个接口,让原本不兼容的两端可以协同工作。
1.1 你的场景该不该用适配器?
| 判断标准 | 是 → 用适配器 | 否 → 用其他方式 |
|---|---|---|
| 需要对接的类源码不能修改(第三方SDK、老系统) | ✅ | ❌ |
| 已有系统有统一的接口规范,新组件接口不兼容 | ✅ | ❌ |
| 只是想给现有类增加功能,接口本身没变 | ❌ | 用装饰器模式 |
| 只是想控制访问,接口本身没变 | ❌ | 用代理模式 |
2. 模式定义与 UML 结构
适配器模式 将一个类的接口转换成客户期望的另一个接口,使得原本因接口不兼容而无法一起工作的类可以合作。它属于 结构型设计模式。
适配器模式有三种形态:类适配器(继承) 、对象适配器(组合) 、缺省适配器(空实现)。
2.1 类适配器(继承 Adaptee)

角色说明:
- Target:客户端期望的目标接口;
- Adaptee:原有的类,接口不兼容,需要被适配;
- Adapter :适配器,继承
Adaptee并实现Target接口,在request()中调用specificRequest()。
2.2 对象适配器(持有 Adaptee 实例)

✅ 对象适配器通过组合代替继承,更灵活,是实际开发中的首选。
2.3 缺省适配器
当 Target 是一个有很多方法的接口,而你只想对其中的个别方法感兴趣时,先用一个抽象类提供所有方法的空实现,子类只需重写关心的方法。
3. 代码实现:物流轨迹适配器(对象适配器,推荐)
3.1 系统已有的目标接口(Target)
java
/**
* WMS 系统统一的物流轨迹接口
*/
public interface TrajectoryService {
TrajectoryResult query(TrajectoryQuery query);
}
3.2 已有系统的参数和返回值类型
java
// 查询参数
public class TrajectoryQuery {
private String waybillNo; // 运单号
private String carrier; // 承运商编码
// getters/setters...
}
// 查询结果
public class TrajectoryResult {
private String status; // 状态
private String location; // 当前位置
// getters/setters...
}
3.3 第三方 SDK(Adaptee,不可修改)
java
// 第三方提供的物流轨迹查询类,不可修改源码
public class LogisticsTracker {
public String queryTrajectory(String waybillNo, String carrierCode) {
// 实际会调用第三方 API
return "{\"status\":\"运输中\",\"location\":\"北京分拨中心\"}";
}
}
注意:这个方法参数是两个字符串,返回的是 JSON。与系统统一的
TrajectoryService接口完全不兼容。
3.4 对象适配器(推荐)
java
/**
* 物流轨迹适配器
* 实现系统统一的 TrajectoryService 接口
* 内部持有第三方 LogisticsTracker 实例
*/
public class LogisticsTrackerAdapter implements TrajectoryService {
private LogisticsTracker tracker; // 持有 Adaptee
public LogisticsTrackerAdapter(LogisticsTracker tracker) {
this.tracker = tracker;
}
@Override
public TrajectoryResult query(TrajectoryQuery query) {
// 1. 参数转换:把 WMS 统一参数转为第三方需要的格式
String waybillNo = query.getWaybillNo();
String carrierCode = query.getCarrier();
// 2. 调用第三方方法
String jsonResponse = tracker.queryTrajectory(waybillNo, carrierCode);
// 3. 结果转换:把 JSON 转为 WMS 统一的结果对象
return parseJsonToResult(jsonResponse);
}
private TrajectoryResult parseJsonToResult(String json) {
// 简化的 JSON 解析(实际项目中用 Jackson/Gson)
TrajectoryResult result = new TrajectoryResult();
if (json.contains("运输中")) {
result.setStatus("运输中");
}
if (json.contains("北京分拨中心")) {
result.setLocation("北京分拨中心");
}
return result;
}
}
✅ 适配器完成了"参数转换 + 方法调用 + 结果转换"三个动作,让第三方 SDK 无缝融入到 WMS 统一接口中。
3.5 客户端调用
java
// 业务层代码,只依赖 TrajectoryService 接口
public class WaybillService {
private TrajectoryService trajectoryService;
public WaybillService(TrajectoryService trajectoryService) {
this.trajectoryService = trajectoryService;
}
public void trackWaybill(String waybillNo, String carrier) {
TrajectoryQuery query = new TrajectoryQuery();
query.setWaybillNo(waybillNo);
query.setCarrier(carrier);
TrajectoryResult result = trajectoryService.query(query);
System.out.println("物流状态:" + result.getStatus() + ",位置:" + result.getLocation());
}
}
// 使用适配器
LogisticsTracker thirdPartyTracker = new LogisticsTracker();
TrajectoryService adapter = new LogisticsTrackerAdapter(thirdPartyTracker);
WaybillService service = new WaybillService(adapter);
service.trackWaybill("YD123456789", "YTO");
// 输出:物流状态:运输中,位置:北京分拨中心
✅ 业务代码只依赖
TrajectoryService接口,完全不知道底层是第三方 SDK 还是自己的实现。后续换物流商,只需新增一个 Adapter 类,业务代码零修改。
4. 类适配器实现(需要时可用)
java
// 继承第三方类,同时实现系统统一接口
public class LogisticsTrackerClassAdapter extends LogisticsTracker implements TrajectoryService {
@Override
public TrajectoryResult query(TrajectoryQuery query) {
String json = queryTrajectory(
query.getWaybillNo(),
query.getCarrier()
);
return parseJsonToResult(json);
}
private TrajectoryResult parseJsonToResult(String json) { /* ... */ }
}
局限 :Java 单继承,Adapter 继承了 LogisticsTracker 就无法再继承其他类。如果有多个 Adaptee 需要适配,类适配器无法处理。
✅ 日常开发首选对象适配器,除非你需要重写 Adaptee 的某些方法。
5. 进阶:Spring MVC 源码剖析 ------ HandlerAdapter 为什么是适配器?
适配器模式在顶级框架中扮演着"统一接口"的关键角色。Spring MVC 的 DispatcherServlet 需要调用各种不同类型的 Handler,但每种 Handler 的调用方式不同。它用 HandlerAdapter 来消除这些差异。
5.1 核心类图

5.2 DispatcherServlet 核心逻辑(简化版)
java
public class DispatcherServlet {
private List<HandlerAdapter> handlerAdapters; // 所有适配器
// 核心调度方法
protected void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
// 1. 获取处理器
Object handler = getHandler(req);
// 2. 找到支持该处理器的适配器
HandlerAdapter adapter = getHandlerAdapter(handler);
// 3. 统一调用
ModelAndView mv = adapter.handle(req, resp, handler);
// 4. 渲染视图...
}
private HandlerAdapter getHandlerAdapter(Object handler) {
for (HandlerAdapter adapter : handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
throw new ServletException("No adapter for handler");
}
}
5.3 不同处理器的适配实现
java
// 适配 @RequestMapping 注解方法
public class RequestMappingHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof HandlerMethod;
}
@Override
public ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
// 反射调用 Controller 方法,处理 @RequestParam、@PathVariable 等
return ((HandlerMethod) handler).invokeAndHandle(req, resp);
}
}
// 适配 HttpRequestHandler 接口
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof HttpRequestHandler;
}
@Override
public ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
((HttpRequestHandler) handler).handleRequest(req, resp);
return null;
}
}
💡 核心价值 :如果没有
HandlerAdapter,DispatcherServlet必须写一长串if-else来判断handler的类型并分别调用。有了适配器模式,新增一种处理器类型只需新增一个 Adapter 实现,核心调度逻辑零修改,完美遵循开闭原则。
6. 扩展:缺省适配器与接口隔离原则
6.1 经典案例:AWT 的 WindowAdapter(遗留场景)
Java AWT 中的 WindowListener 有 7 个方法,而开发者往往只关心窗口关闭事件。缺省适配器提供了所有方法的空实现:
java
// 原始接口(7 个方法)------这是典型的"胖接口"
public interface WindowListener {
void windowOpened();
void windowClosing();
void windowClosed();
void windowIconified();
void windowDeiconified();
void windowActivated();
void windowDeactivated();
}
// 缺省适配器(所有方法空实现)
public abstract class WindowAdapter implements WindowListener {
@Override public void windowOpened() {}
@Override public void windowClosing() {}
@Override public void windowClosed() {}
@Override public void windowIconified() {}
@Override public void windowDeiconified() {}
@Override public void windowActivated() {}
@Override public void windowDeactivated() {}
}
// 客户端只需继承适配器,重写关心的方法
public class MyWindowListener extends WindowAdapter {
@Override
public void windowClosing() {
System.out.println("窗口正在关闭,保存数据...");
}
}
6.2 现代 Java 的最佳实践
⚠️ 局限性 :缺省适配器是"先污染后治理"------在设计接口时过于臃肿,再用适配器去补救。在现代 Java 开发中,应该遵循接口隔离原则(ISP),在设计阶段就把大接口拆分为多个小接口。
更好的方案:使用 default 方法 + 函数式接口
java
// 使用函数式接口 + default 方法替代胖接口
public interface ModernWindowListener {
default void onOpen() {}
default void onClose() {}
default void onFocus() {}
// 客户端只需实现关心的回调
static ModernWindowListener onClose(Consumer<Void> callback) {
return new ModernWindowListener() {
@Override
public void onClose() { callback.accept(null); }
};
}
}
✅ 现代最佳实践:如果接口是新设计的,尽量遵循接口隔离原则拆分为小接口。只有当对接遗留的"胖接口"时(如第三方 SDK 或老框架),才使用缺省适配器作为补救手段。
7. 现代 Java:异步与泛型适配
7.1 异步适配:把回调转为 CompletableFuture
现代 Java 应用越来越多使用异步编程。假设第三方 SDK 提供的是回调式接口,而你的系统统一使用 CompletableFuture:
java
// 第三方物流 SDK(回调风格)
public class AsyncLogisticsTracker {
public void queryTrajectoryAsync(String waybillNo, TrajectoryCallback callback) {
// 异步查询,完成后回调
new Thread(() -> {
String result = "{\"status\":\"运输中\"}";
callback.onSuccess(result);
}).start();
}
}
public interface TrajectoryCallback {
void onSuccess(String json);
void onError(Exception e);
}
异步适配器:将回调转为 CompletableFuture:
java
import java.util.concurrent.CompletableFuture;
/**
* 异步适配器:将回调式接口适配为 CompletableFuture
*/
public class AsyncTrajectoryAdapter {
private AsyncLogisticsTracker asyncTracker;
public AsyncTrajectoryAdapter(AsyncLogisticsTracker asyncTracker) {
this.asyncTracker = asyncTracker;
}
// 核心:把回调转成 CompletableFuture
public CompletableFuture<TrajectoryResult> queryAsync(TrajectoryQuery query) {
CompletableFuture<TrajectoryResult> future = new CompletableFuture<>();
asyncTracker.queryTrajectoryAsync(
query.getWaybillNo(),
new TrajectoryCallback() {
@Override
public void onSuccess(String json) {
future.complete(parseJsonToResult(json));
}
@Override
public void onError(Exception e) {
future.completeExceptionally(e);
}
}
);
return future;
}
private TrajectoryResult parseJsonToResult(String json) { /* ... */ }
}
客户端调用:
java
AsyncTrajectoryAdapter adapter = new AsyncTrajectoryAdapter(new AsyncLogisticsTracker());
adapter.queryAsync(query)
.thenAccept(result -> System.out.println("物流状态:" + result.getStatus()))
.exceptionally(e -> { System.out.println("查询失败"); return null; });
💡 适配器在这里不仅仅是"参数格式"的转换,更是"调用模型"的转换------从回调模型适配为
Future模型,这正是适配器在现代 Java 中的深度应用。
7.2 泛型适配器:一次编写,到处适配
利用 Java 泛型 + 函数式接口,可以编写通用的适配器基类:
java
import java.util.function.Function;
/**
* 泛型适配器
* @param <S> 源类型(Adaptee 返回的类型)
* @param <T> 目标类型(客户端期望的类型)
*/
public class GenericAdapter<S, T> implements TrajectoryService {
private Function<TrajectoryQuery, S> sourceCaller; // 如何调用 Adaptee
private Function<S, T> converter; // 如何转换结果
public GenericAdapter(Function<TrajectoryQuery, S> sourceCaller, Function<S, T> converter) {
this.sourceCaller = sourceCaller;
this.converter = converter;
}
@Override
@SuppressWarnings("unchecked")
public TrajectoryResult query(TrajectoryQuery query) {
S sourceResult = sourceCaller.apply(query);
T targetResult = converter.apply(sourceResult);
return (TrajectoryResult) targetResult;
}
}
// 使用泛型适配器(无需为每个平台写一个 Adapter 类)
LogisticsTracker tracker = new LogisticsTracker();
GenericAdapter<String, TrajectoryResult> adapter = new GenericAdapter<>(
query -> tracker.queryTrajectory(query.getWaybillNo(), query.getCarrier()),
json -> parseJsonToResult(json)
);
✅ 泛型适配器将"调用方式"和"转换逻辑"参数化,减少了大量样板类,在需要适配多种格式时特别实用。
8. 优缺点一览
| 优点 | 缺点 |
|---|---|
| 解耦:目标类与适配者类解耦,通过引入适配器重用现有类 | 类适配器受限于 Java 单继承 |
| 透明:客户端通过同一接口操作,无需关心内部适配细节 | 过多的适配器会让系统变得凌乱 |
| 灵活:对象适配器可以适配多个不同的 Adaptee 及其子类 | 对象适配器若需替换 Adaptee 的方法,比较困难 |
| 高复用:不需要修改原有类,完全符合开闭原则 | 对接口差异巨大的场景,适配器可能写得较复杂 |
9. 适配器模式 vs 装饰器模式 vs 代理模式
| 对比维度 | 适配器模式 | 装饰器模式 | 代理模式 |
|---|---|---|---|
| 目的 | 转换接口 | 增强功能 | 控制访问 |
| 是否改变接口 | ✅ 改变 | ❌ 不改变 | ❌ 不改变 |
| 与被包装对象关系 | 一对一 | 可层层包装 | 一对一 |
| 典型应用 | InputStreamReader |
BufferedInputStream |
Spring AOP |
💡 简单记忆:适配器是"翻译官"(换接口),装饰器是"加料师"(不换接口加功能),代理是"中介人"(不换接口控访问)。
10. 框架与实践中的应用
10.1 JDK I/O:InputStreamReader / OutputStreamWriter
InputStream 是字节流,Reader 是字符流。InputStreamReader 内部持有 InputStream,将其适配成 Reader,完成字节到字符的转换。
java
Reader reader = new InputStreamReader(
new FileInputStream("data.txt"), StandardCharsets.UTF_8
);
10.2 Spring MVC:HandlerAdapter(详见第 5 节)
10.3 电子面单对接
在《电商多平台电子面单对接实战》电子面单项目中,物流轨迹同样涉及多平台适配:抖音、淘宝、拼多多各有自己的轨迹查询 API。通过适配器模式,为每个平台实现一个 Adapter,业务层只依赖统一的 TrajectoryService 接口,新增平台只需新增一个 Adapter 类。
详细请关注《电商多平台电子面单对接实战》系列文章,更多设计模式参见【电子面单对接实战|开篇】从"能跑就行"到"整洁架构"------WMS多平台发货系统重构手记 目录
11. AI 时代的适配器模式
在 AI 辅助编程时代,适配器模式的代码可以由 AI 快速生成。你只需要清晰地描述需求:
推荐的 Prompt 模板 :
"请帮我写一个适配器,实现
Target接口,内部持有Adaptee实例。调用Target.request()时,把参数 A 转换为参数 B,调用Adaptee.specificMethod(B),最后把返回值转换为Target期望的返回类型。使用对象适配器方式(组合),不要修改 Adaptee 源码。"
有了这个 Prompt,AI 可以在几秒内生成你需要的适配器骨架代码,你只需填充转换逻辑即可。
12. 常见误区与面试高频题
❌ 误区1:适配器模式与装饰器模式一模一样
适配器改变接口,装饰器增强功能但不改变接口。InputStreamReader 是把字节流接口转成字符流接口(适配器),BufferedInputStream 是给字节流加上缓冲功能(装饰器)。
❌ 误区2:有不同接口就应该用适配器
如果是新开发的系统,且两个模块都由你控制,更好的方案是统一接口设计,而不是后期加适配器。适配器的核心价值在于"不修改已有代码"。
💡 面试高频追问
- 适配器模式分为哪几种?区别在哪? → 类适配器(继承)、对象适配器(组合)、缺省适配器(抽象空实现)。对象适配器更灵活,是实际开发首选。
InputStreamReader用了哪种适配器? → 对象适配器。它内部持有InputStream实例。- Spring MVC 中哪里用了适配器模式? →
HandlerAdapter将不同类型的Handler适配给DispatcherServlet统一调用。 - 适配器模式和策略模式的区别? → 适配器用于解决接口不兼容,策略用于算法的封装和替换。
🎉 恭喜 :如果你能立刻说出"
InputStreamReader是适配器模式,BufferedInputStream是装饰器模式",并理解 Spring 为什么用HandlerAdapter而不是if-else,你已经掌握了结构型模式中最核心的区分点和框架设计思想。
13. 六大设计原则在适配器模式中的体现
| 设计原则 | 体现 |
|---|---|
| 单一职责(SRP) | 适配器只负责接口转换,不涉及业务逻辑变更 |
| 开闭原则(OCP) | 通过新增适配器类扩展兼容性,无需修改原有类或第三方 SDK |
| 里氏替换(LSP) | 客户端使用 Target 接口,任何 Adapter 实现都可替换 |
| 依赖倒置(DIP) | 客户端依赖抽象 Target 接口,不依赖具体适配器 |
| 接口隔离(ISP) | 缺省适配器避免强制实现不需要的方法;新设计应拆分大接口 |
| 迪米特法则(LoD) | 客户端只需知道 Target 接口,无需了解 Adaptee 的存在 |
🧭 《Java 23 种设计模式:从踩坑到精通》快速导航
- 开篇:系列介绍与目录
- 上一篇:原型模式 ------ 克隆对象,深拷贝与浅拷贝的坑你踩过吗?
- 当前:适配器模式 ------ 让不兼容的接口也能一起工作(你在这里)
- 下一篇:桥接模式 ------ 类爆炸?试试分离抽象与实现
- 创建型模式汇总:单例、工厂、建造者、原型
- 结构型模式汇总:适配器、装饰器、代理......
- 行为型模式汇总:观察者、策略、模板方法......
🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦 福利预告 :全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
🚀 下一篇 :桥接模式 ------ 类爆炸?试试分离抽象与实现!已发布,欢迎前去阅读相关设计细节。
📌 除了设计模式,我也在深挖智能物流实战 (WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》、《电商多平台电子面单对接实战》。技术相通,思路可鉴。