适配器模式:化解接口不兼容的桥梁设计
一、模式核心:让不兼容的接口协同工作
在软件开发中,经常会遇到接口不兼容的情况:如旧系统提供的接口无法被新系统直接调用,或第三方库的接口与当前系统设计不匹配。适配器模式(Adapter Pattern) 通过创建一个中间适配器,将一个接口转换为另一个接口,使原本不兼容的类可以协同工作,核心解决:
- 接口转换:将一个类的接口适配为另一个接口
- 复用 legacy 代码:在不修改原有代码的前提下,让旧组件兼容新系统
核心思想与 UML 类图(PlantUML 语法)
适配器模式分为对象适配器(组合方式)和类适配器(继承方式),典型的对象适配器结构如下:
二、核心实现:适配旧支付接口到新系统
1. 定义目标接口(新系统期望的接口)
// 新系统使用的支付接口
public interface PaymentTarget {
void pay(String orderId, double amount); // 支付方法
}
2. 现有适配者(旧系统的支付接口,方法名不兼容)
// 旧系统支付接口(方法名为payOld)
public class LegacyPayment {
public void payOld(String orderId, double amount) {
System.out.println("旧支付接口处理订单:" + orderId + ",金额:" + amount);
}
}
3. 创建对象适配器(通过组合适配旧接口)
public class PaymentAdapter implements PaymentTarget {
private LegacyPayment legacyPayment; // 持有旧接口对象
public PaymentAdapter(LegacyPayment legacyPayment) {
this.legacyPayment = legacyPayment;
}
@Override
public void pay(String orderId, double amount) {
// 将新接口的pay方法适配为旧接口的payOld方法
legacyPayment.payOld(orderId, amount);
}
}
4. 客户端使用适配器(透明调用新接口)
public class ClientDemo {
public static void main(String[] args) {
// 创建旧接口对象
LegacyPayment legacyPayment = new LegacyPayment();
// 创建适配器,将旧接口适配为新接口
PaymentTarget adapter = new PaymentAdapter(legacyPayment);
// 通过新接口调用支付功能
adapter.pay("ORDER_20231001", 998.0);
}
}
三、进阶:实现双向适配器与多接口适配
1. 双向适配器(同时适配两个方向的接口)
// 新接口(目标接口)
public interface NewService {
void newMethod();
}
// 旧接口(适配者)
public interface OldService {
void oldMethod();
}
// 双向适配器
public class BidirectionalAdapter implements NewService, OldService {
private NewService newService;
private OldService oldService;
public BidirectionalAdapter(NewService newService, OldService oldService) {
this.newService = newService;
this.oldService = oldService;
}
// 适配旧接口到新接口
@Override
public void newMethod() {
oldService.oldMethod();
}
// 适配新接口到旧接口
@Override
public void oldMethod() {
newService.newMethod();
}
}
2. 多接口适配器(适配多个不兼容接口)
public interface LogService {
void log(String message);
}
public interface NotifyService {
void sendNotification(String message);
}
public class LegacySystem {
public void writeToLog(String logMsg) { /* 旧日志方法 */ }
public void sendAlert(String alertMsg) { /* 旧通知方法 */ }
}
public class MultiAdapter implements LogService, NotifyService {
private LegacySystem legacySystem;
public MultiAdapter(LegacySystem legacySystem) {
this.legacySystem = legacySystem;
}
@Override
public void log(String message) {
legacySystem.writeToLog("LOG: " + message);
}
@Override
public void sendNotification(String message) {
legacySystem.sendAlert("ALERT: " + message);
}
}
3. 可视化适配流程
四、框架与源码中的适配器实践
1. Spring MVC 适配器(HandlerAdapter)
- 适配不同类型的处理器(Handler),如
Controller
、HttpRequestHandler
// 适配@Controller标注的类
public class AnnotationMethodHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof Controller;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 将请求适配到Controller的方法
return handleInternal(request, response, (Controller) handler);
}
}
2. AWT/Swing 事件适配器(Event Adapter)
- 简化事件监听器的实现,如
MouseAdapter
提供空方法实现
// 继承MouseAdapter,仅重写需要的方法
public class MyMouseListener extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
// 处理点击事件
}
}
3. 数据库驱动适配器(JDBC Driver)
- JDBC 驱动将不同数据库的原生接口适配为统一的 JDBC 接口
// MySQL驱动适配器(简化示例)
public class MySQLDriver implements Driver {
@Override
public Connection connect(String url, Properties info) {
// 适配MySQL的原生连接方法
return new MySQLConnection(url, info);
}
}
五、避坑指南:正确使用适配器模式的 3 个要点
1. 优先使用对象适配器(组合优于继承)
- ❌ 类适配器需要继承适配者类,若适配者是 final 类则无法使用
- ✅ 对象适配器通过组合适配者对象,更灵活且符合合成复用原则
2. 避免过度适配(保持接口简洁)
- 适配器应聚焦于接口转换,避免包含复杂业务逻辑
- 复杂转换逻辑可提取为独立的
Converter
工具类
3. 明确适配范围(避免职责扩散)
- 一个适配器应只适配一个适配者或一组相关接口
- 若需要适配多个无关接口,考虑拆分为多个适配器
4. 反模式:滥用适配器掩盖设计缺陷
- 若系统中存在大量适配器,可能是接口设计不合理导致,需重新审视架构
六、总结:何时该用适配器模式?
适用场景 | 核心特征 | 典型案例 |
---|---|---|
复用遗留系统接口 | 旧接口与新系统不兼容,但无法修改旧代码 | 整合 ERP 系统、迁移老旧微服务 |
使用第三方库 | 第三方库接口与现有系统设计不匹配 | 集成支付网关、物流 API |
统一不同接口格式 | 需要将多种接口转换为统一的公共接口 | 日志系统(适配不同日志格式) |
适配器模式通过 “接口转换 + 中间层封装” 的设计,为不兼容的接口搭建了沟通的桥梁,是解决系统集成问题的有效方案。下一篇我们将深入探讨桥接模式,解析如何分离抽象与实现的独立变化,敬请期待!
扩展思考:适配器模式 vs 外观模式
两者都涉及对接口的封装,但核心目标不同:
模式 | 封装目的 | 接口关系 | 典型应用 |
---|---|---|---|
适配器模式 | 转换接口格式,解决不兼容问题 | 适配者与目标接口独立 | 旧系统对接、API 格式转换 |
外观模式 | 提供简化的统一接口 | 外观接口整合多个子系统接口 | 框架入口、复杂模块简化调用 |
理解这种差异,能帮助我们在设计时选择更合适的模式来优化系统结构。