Java设计模式-适配器模式
模式概述
适配器模式简介
核心思想:通过定义一个适配器类,将被适配者(Adaptee)的接口转换为客户端期望的目标接口(Target),使得原本因接口不兼容而无法协作的类能够协同工作。本质是"接口转换器",解决不同系统或模块间的接口适配问题。
模式类型:结构型设计模式(关注对象的组成与接口适配)。
作用:
- 解决接口不兼容:让不兼容的接口通过适配器协同工作(如旧系统接口适配新需求)。
- 复用已有类:无需修改被适配者的代码,直接复用其功能(如集成第三方库)。
- 提高系统灵活性:通过适配器隔离接口差异,降低模块间耦合(如支持多类型数据源接入)。
典型应用场景:
- 第三方库/服务接口适配(如调用微信支付接口与系统内部支付接口不兼容)。
- 旧系统升级改造(如将遗留系统的
LegacyUserService
接口适配为新系统要求的NewUserService
接口)。 - 多数据源整合(如将MySQL的
ResultSet
适配为Redis的HashOperations
接口)。 - 统一抽象层(如为不同日志框架(Log4j/SLF4J)提供统一日志接口)。
我认为:适配器模式是"接口翻译官",用最小成本让"方言"与"普通话"互通,是系统集成与旧代码改造的必备工具。
课程目标
- 理解适配器模式的核心思想和经典应用场景
- 识别应用场景,使用适配器模式解决功能要求
- 了解适配器模式的优缺点
核心组件
角色-职责表
角色 | 职责 | 示例类名 |
---|---|---|
目标接口(Target) | 定义客户端期望的接口(如Payment ),声明需要实现的方法 |
Payment |
被适配者(Adaptee) | 已存在的、需要被适配的类(如第三方支付类WeChatPay ),拥有不兼容接口 |
WeChatPay |
适配器(Adapter) | 实现目标接口,包装被适配者实例,将其方法转换为目标接口的方法 | WeChatPayAdapter |
类图
下面是一个简化的类图表示,展示了适配器模式中的主要角色及其交互方式:
实现 持有 使用 创建 创建空 <<interface>> Payment +pay(amount: double) WeChatPay -appId: String +WeChatPay(appId: String) +specificPay(openId: String, amount: double) WeChatPayAdapter -adaptee: WeChatPay -openId: String +WeChatPayAdapter(adaptee: WeChatPay, openId: String) +pay(amount: double) Client +main(args: String[])
传统实现 VS 适配器模式
案例需求
案例背景 :某电商平台需要接入微信支付功能,但微信支付的SDK提供的支付方法是WeChatPay.specificPay(String openId, double amount)
(需传入用户openId
),而电商系统内部统一的支付接口是Payment.pay(double amount)
(仅需金额)。直接调用会导致接口不兼容。
传统实现(痛点版)
代码实现:
java
// 目标接口:系统内部统一的支付接口
//interface Payment {
// void pay(double amount); // 仅需金额
//}
// 被适配者:微信支付SDK(第三方类,不可修改)
class WeChatPay {
private String appId; // 微信应用ID(需初始化)
public WeChatPay(String appId) {
this.appId = appId;
}
// 微信支付的实际方法(需openId和金额,与系统接口不兼容)
public void specificPay(String openId, double amount) {
System.out.printf("微信支付:用户%s支付%.2f元%n", openId, amount);
}
}
// 传统实现:客户端直接处理接口差异(硬编码适配)
public class Client {
public static void main(String[] args) {
WeChatPay weChatPay = new WeChatPay("wx_app_123");
double amount = 99.9;
// 问题:需在客户端硬编码openId(违反单一职责,且无法复用)
weChatPay.specificPay("user_openid_456", amount);
// 若需适配其他支付方式(如支付宝),需重复编写类似代码
}
}
痛点总结:
- 接口强耦合 :客户端需直接处理被适配者的特殊参数(如
openId
),违反"依赖倒置原则"。 - 代码冗余:每个被适配者需在客户端重复编写适配逻辑(如新增支付宝支付需复制粘贴类似代码)。
- 可维护性差 :若被适配者接口变更(如
specificPay
参数调整),所有客户端代码需同步修改。
适配器模式 实现(优雅版)
代码实现:
java
// 1. 目标接口:系统内部统一的支付接口
interface Payment {
void pay(double amount); // 客户端仅需传递金额
}
// 2. 被适配者:微信支付SDK(第三方类,不可修改)
class WeChatPay {
private String appId;
public WeChatPay(String appId) {
this.appId = appId;
}
// 微信支付的原始方法(需openId和金额)
public void specificPay(String openId, double amount) {
System.out.printf("微信支付:用户%s支付%.2f元%n", openId, amount);
}
}
// 3. 适配器:将微信支付接口适配为系统统一的Payment接口
class WeChatPayAdapter implements Payment {
private final WeChatPay adaptee; // 持有被适配者实例
private final String openId; // 微信支付需要的额外参数(封装在适配器中)
public WeChatPayAdapter(WeChatPay adaptee, String openId) {
this.adaptee = adaptee;
this.openId = openId;
}
@Override
public void pay(double amount) {
// 调用被适配者的specificPay方法,传入封装的openId和amount
adaptee.specificPay(openId, amount);
}
}
// 4. 客户端使用:通过适配器间接调用微信支付
public class Client {
public static void main(String[] args) {
WeChatPay weChatPay = new WeChatPay("wx_app_123");
String openId = "user_openid_456"; // 微信用户ID(仅在适配器初始化时传入)
Payment payment = new WeChatPayAdapter(weChatPay, openId); // 适配器完成接口转换
payment.pay(99.9); // 客户端仅需调用统一接口,无需关心微信支付的细节
// 输出:微信支付:用户user_openid_456支付99.90元
}
}
优势:
- 接口解耦 :客户端仅依赖目标接口(
Payment
),无需感知被适配者的特殊参数(如openId
)。 - 代码复用:适配器封装接口转换逻辑,新增支付方式(如支付宝)只需编写新的适配器类,客户端无感知。
- 可维护性高 :被适配者接口变更时(如
specificPay
参数调整),仅需修改适配器类,无需改动客户端代码。
局限:
- 类数量增加 :每个被适配者需定义一个适配器类(如
WeChatPayAdapter
、AlipayAdapter
),可能增加系统类膨胀。 - 过度设计风险:若仅需适配单个接口且调用频率低,直接修改客户端代码可能更简单(需权衡长期维护成本)。
- 适配器复杂性:若被适配者接口与目标接口差异极大(如方法名、参数类型完全不同),适配器实现可能较复杂。
模式变体
- 类适配器(Class Adapter) :通过继承被适配者类实现目标接口(需被适配者非
final
),相比对象适配器更简洁,但受限于单继承(Java/C#不支持多继承)。 - 对象适配器(Object Adapter):通过组合持有被适配者实例实现目标接口(如上述案例),更灵活(支持多适配),是更常用的变体。
- 双向适配器(Bidirectional Adapter) :同时实现两个接口(目标接口和被适配者接口),允许两个不兼容的类相互调用(如
JavaList
适配PythonList
)。 - 缺省适配器(Default Adapter):为目标接口提供默认实现,子类仅需重写需要的方法(如GUI事件监听器,避免实现所有空方法)。
最佳实践
建议 | 理由 |
---|---|
优先使用对象适配器 | 组合模式更灵活(支持多适配),避免单继承限制(如Java的final 类无法被继承)。 |
适配器职责单一 | 仅负责接口转换,不添加额外逻辑(如参数校验、业务处理),保持代码清晰。 |
明确接口契约 | 目标接口需与客户端期望严格一致,避免适配器隐藏关键逻辑导致调试困难。 |
封装被适配者的依赖 | 适配器初始化时传入被适配者实例(如WeChatPay ),而非在适配器内部创建,提高测试灵活性(支持Mock)。 |
添加单元测试 | 验证适配器的接口转换逻辑(如输入金额是否正确传递给被适配者)。 |
一句话总结
适配器模式通过"接口转换"解决了不同系统或模块间的接口不兼容问题,是复用已有功能、降低系统耦合的关键工具,尤其适用于集成第三方库或旧系统改造场景。
如果关注Java设计模式内容,可以查阅作者的其他Java设计模式系列文章。😊