适配器模式:设计与实践
一、什么是适配器模式
1. 基本定义
适配器模式(Adapter Pattern)是一种结构型设计模式,由《设计模式:可复用面向对象软件的基础》(GOF著作)定义为:将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
该模式通过引入一个适配器类,在不修改原有接口的前提下,实现两个不兼容接口之间的协作。适配器接收客户端的请求,将其转换为被适配者能理解的格式,从而实现客户端与被适配者的间接交互。
2. 核心思想
适配器模式的核心在于接口转换与兼容。它通过封装不兼容的接口,为客户端提供统一的访问入口,解决了"接口异构"问题。这种设计既保护了现有代码的稳定性,又能满足新的业务需求,是系统集成与扩展的重要手段。
二、适配器模式的特点
1. 接口转换
将不兼容的接口转换为客户端期望的接口,实现不同接口之间的通信。
2. 兼容性增强
使原本不能一起工作的类可以协同工作,解决接口不匹配问题。
3. 透明性
客户端通过适配器访问被适配者,无需感知被适配者的接口细节,实现透明调用。
4. 复用性
复用现有组件(被适配者),无需修改其代码即可接入新系统,提高代码复用率。
5. 低侵入性
无需修改客户端和被适配者的代码,仅通过添加适配器实现兼容,符合开闭原则。
| 特点 | 说明 |
|---|---|
| 接口转换 | 将不兼容接口转换为客户端期望的接口格式 |
| 兼容性增强 | 解决接口不匹配问题,使异构组件可协同工作 |
| 透明性 | 客户端无需感知被适配者的接口细节,调用方式统一 |
| 复用性 | 复用现有组件,无需修改源码即可接入新系统 |
| 低侵入性 | 仅通过适配器实现兼容,不修改原有代码 |
三、适配器模式的标准代码实现
1. 模式结构
适配器模式包含四个核心角色:
- 目标接口(Target):客户端期望的接口,定义客户端需要的方法
- 被适配者(Adaptee):现有组件的接口,与目标接口不兼容
- 适配器(Adapter):实现目标接口,持有被适配者的引用,负责接口转换
- 客户端(Client):使用目标接口与适配器交互,无需感知被适配者
2. 代码实现示例
2.1 目标接口(Target)
/**
* 目标接口
* 定义客户端期望的接口
*/
public interface Target {
void request();
}
2.2 被适配者(Adaptee)
/**
* 被适配者
* 现有组件的接口,与目标接口不兼容
*/
public class Adaptee {
/**
* 被适配者的特定方法
*/
public void specificRequest() {
System.out.println("被适配者执行特定操作");
}
}
2.3 对象适配器(Object Adapter)
/**
* 对象适配器
* 通过组合被适配者实现接口转换
*/
public class ObjectAdapter implements Target {
// 持有被适配者的引用(组合方式)
private final Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
// 将目标接口的请求转换为被适配者的方法调用
adaptee.specificRequest();
}
}
2.4 类适配器(Class Adapter)
/**
* 类适配器
* 通过继承被适配者实现接口转换(Java中因单继承限制,应用较少)
*/
public class ClassAdapter extends Adaptee implements Target {
@Override
public void request() {
// 调用父类(被适配者)的特定方法
super.specificRequest();
}
}
2.5 客户端使用示例
/**
* 客户端
* 通过目标接口与适配器交互
*/
public class Client {
public static void main(String[] args) {
// 1. 使用对象适配器
Adaptee adaptee = new Adaptee();
Target objectAdapter = new ObjectAdapter(adaptee);
System.out.println("=== 对象适配器调用 ===");
objectAdapter.request();
// 2. 使用类适配器
Target classAdapter = new ClassAdapter();
System.out.println("=== 类适配器调用 ===");
classAdapter.request();
}
}
3. 代码实现特点总结
| 适配器类型 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 对象适配器 | 实现目标接口,持有被适配者引用(组合) | 不破坏继承关系,可适配多个被适配者,灵活性高 | 需编写更多代码转发请求 | 大多数场景,尤其是被适配者可能变化时 |
| 类适配器 | 继承被适配者,实现目标接口(继承) | 代码简洁,直接调用父类方法 | 受单继承限制,只能适配一个被适配者 | 被适配者稳定且无需适配多个源时 |
四、支付框架设计中适配器模式的运用
以对账文件解析适配为例,说明适配器模式在支付系统中的具体实现:
1. 场景分析
支付系统每日需与多个支付渠道(支付宝、微信支付、银联等)进行对账,各渠道的对账文件格式差异显著:
- 支付宝:CSV格式,字段顺序为
交易时间,商户订单号,渠道交易号,金额,状态 - 微信支付:XML格式,包含
<transaction_id>、<out_trade_no>、<total_fee>等标签 - 银联:固定长度文本格式,前14位为交易日期,15-34位为商户订单号,35-48位为金额(分)
支付系统需要将这些异构格式的文件统一解析为内部ReconciliationRecord对象,通过适配器模式可屏蔽格式差异,使对账核心逻辑专注于业务处理。
2. 设计实现
2.1 对账记录统一模型(目标类型)
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 对账记录统一模型
* 所有渠道的对账数据最终都转换为此模型
*/
public class ReconciliationRecord {
private String orderId; // 商户订单号
private String channelTradeNo; // 渠道交易号
private BigDecimal amount; // 交易金额(元)
private LocalDateTime tradeTime; // 交易时间
private String status; // 交易状态(SUCCESS/FAIL)
// getter和setter ...
}
2.2 对账文件解析器接口(目标接口)
import java.io.InputStream;
import java.util.List;
/**
* 对账文件解析器接口(目标接口)
* 定义统一的对账文件解析方法
*/
public interface ReconciliationFileParser {
/**
* 解析对账文件
* @param inputStream 文件输入流
* @return 统一格式的对账记录列表
*/
List<ReconciliationRecord> parse(InputStream inputStream);
}
2.3 各渠道对账文件解析适配器
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.*;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
/**
* 支付宝CSV文件解析适配器
* 适配支付宝CSV格式对账文件
*/
public class AlipayCsvAdapter implements ReconciliationFileParser {
// 支付宝CSV字段顺序:交易时间,商户订单号,渠道交易号,金额,状态
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public List<ReconciliationRecord> parse(InputStream inputStream) {
List<ReconciliationRecord> records = new ArrayList<>();
try (Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
CSVParser parser = new CSVParser(reader, CSVFormat.DEFAULT.withFirstRecordAsHeader())) {
for (CSVRecord csvRecord : parser) {
ReconciliationRecord record = new ReconciliationRecord();
// 转换字段:支付宝格式→统一格式
record.setTradeTime(LocalDateTime.parse(csvRecord.get("交易时间"), DATE_FORMATTER));
record.setOrderId(csvRecord.get("商户订单号"));
record.setChannelTradeNo(csvRecord.get("渠道交易号"));
record.setAmount(new BigDecimal(csvRecord.get("金额")));
// 转换状态:支付宝"成功"→统一"SUCCESS"
record.setStatus("成功".equals(csvRecord.get("状态")) ? "SUCCESS" : "FAIL");
records.add(record);
}
} catch (IOException e) {
throw new RuntimeException("支付宝CSV对账文件解析失败", e);
}
return records;
}
}
/**
* 微信XML文件解析适配器
* 适配微信XML格式对账文件
*/
public class WechatXmlAdapter implements ReconciliationFileParser {
@Override
public List<ReconciliationRecord> parse(InputStream inputStream) {
List<ReconciliationRecord> records = new ArrayList<>();
try {
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
Element root = document.getRootElement();
// 遍历所有交易记录节点
for (Element recordElem : root.elements("record")) {
ReconciliationRecord record = new ReconciliationRecord();
// 转换字段:微信XML→统一格式
record.setTradeTime(LocalDateTime.parse(
recordElem.elementText("trade_time"),
DateTimeFormatter.ofPattern("yyyyMMddHHmmss")
));
record.setOrderId(recordElem.elementText("out_trade_no"));
record.setChannelTradeNo(recordElem.elementText("transaction_id"));
// 微信金额单位为分,转换为元
record.setAmount(new BigDecimal(recordElem.elementText("total_fee"))
.divide(new BigDecimal("100")));
record.setStatus("SUCCESS".equals(recordElem.elementText("result_code")) ? "SUCCESS" : "FAIL");
records.add(record);
}
} catch (DocumentException e) {
throw new RuntimeException("微信XML对账文件解析失败", e);
}
return records;
}
}
/**
* 银联固定长度文件解析适配器
* 适配银联固定格式对账文件
*/
public class UnionpayFixedLengthAdapter implements ReconciliationFileParser {
@Override
public List<ReconciliationRecord> parse(InputStream inputStream) {
List<ReconciliationRecord> records = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(inputStream, StandardCharsets.ISO_8859_1))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.length() < 48) {
continue; // 跳过无效行
}
ReconciliationRecord record = new ReconciliationRecord();
// 解析固定长度字段:银联格式→统一格式
// 1-14位:交易日期(yyyyMMddHHmmss)
String dateStr = line.substring(0, 14);
record.setTradeTime(LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
// 15-34位:商户订单号
record.setOrderId(line.substring(14, 34).trim());
// 35-48位:金额(分)
String amountStr = line.substring(34, 48).trim();
record.setAmount(new BigDecimal(amountStr).divide(new BigDecimal("100")));
// 49-50位:状态(00=成功,其他=失败)
String statusCode = line.length() >= 50 ? line.substring(48, 50) : "99";
record.setStatus("00".equals(statusCode) ? "SUCCESS" : "FAIL");
records.add(record);
}
} catch (IOException e) {
throw new RuntimeException("银联固定长度对账文件解析失败", e);
}
return records;
}
}
2.4 对账文件解析器工厂
import java.util.HashMap;
import java.util.Map;
/**
* 对账文件解析器工厂
* 根据渠道代码获取对应的解析适配器
*/
public class ReconciliationParserFactory {
// 渠道代码→解析器适配器映射
private static final Map<String, ReconciliationFileParser> PARSER_MAP = new HashMap<>();
// 初始化适配器映射
static {
PARSER_MAP.put("ALIPAY", new AlipayCsvAdapter());
PARSER_MAP.put("WECHAT", new WechatXmlAdapter());
PARSER_MAP.put("UNIONPAY", new UnionpayFixedLengthAdapter());
}
/**
* 根据渠道代码获取解析器
*/
public static ReconciliationFileParser getParser(String channelCode) {
ReconciliationFileParser parser = PARSER_MAP.get(channelCode);
if (parser == null) {
throw new IllegalArgumentException("不支持的对账渠道:" + channelCode);
}
return parser;
}
/**
* 注册新的解析适配器(支持动态扩展)
*/
public static void registerParser(String channelCode, ReconciliationFileParser parser) {
PARSER_MAP.put(channelCode, parser);
}
}
2.5 对账核心服务
import java.io.InputStream;
import java.util.List;
/**
* 对账核心服务
* 依赖统一解析接口,无需关心具体格式
*/
public class ReconciliationService {
/**
* 执行对账流程
* @param channelCode 渠道代码
* @param fileStream 对账文件输入流
*/
public void reconcile(String channelCode, InputStream fileStream) {
// 1. 获取对应渠道的解析适配器
ReconciliationFileParser parser = ReconciliationParserFactory.getParser(channelCode);
// 2. 统一解析为对账记录
List<ReconciliationRecord> channelRecords = parser.parse(fileStream);
// 3. 获取系统订单记录(从数据库查询)
List<ReconciliationRecord> systemRecords = querySystemRecords(channelCode);
// 4. 执行对账逻辑(比对金额、状态等)
ReconciliationResult result = compareRecords(channelRecords, systemRecords);
// 5. 处理对账结果(生成差异报告、自动调账等)
processResult(result);
}
// 从数据库查询系统订单记录
private List<ReconciliationRecord> querySystemRecords(String channelCode) {
// 实际实现中会查询数据库
return new ArrayList<>();
}
// 比对渠道记录与系统记录
private ReconciliationResult compareRecords(
List<ReconciliationRecord> channelRecords,
List<ReconciliationRecord> systemRecords) {
// 实际实现中会执行详细比对逻辑
return new ReconciliationResult();
}
// 处理对账结果
private void processResult(ReconciliationResult result) {
// 实际实现中会生成报告或自动处理差异
}
}
3. 模式价值体现
- 格式隔离:对账核心逻辑与具体文件格式解耦,无需感知支付宝/微信/银联的格式差异
- 统一接口:通过
ReconciliationFileParser接口,客户端可一致化调用不同渠道的解析逻辑 - 扩展便捷:新增渠道(如PayPal)时,只需添加对应的解析适配器,无需修改对账核心代码
- 容错集中:可在适配器中统一处理格式错误(如字段缺失、格式不符),提高系统健壮性
- 配置驱动:通过工厂类动态选择适配器,支持通过配置灵活切换解析逻辑
五、开源框架中适配器模式的运用
以Spring MVC框架中的HandlerAdapter为例,说明适配器模式在框架级别的应用:
1. 核心实现分析
Spring MVC需要处理多种类型的控制器(如@Controller注解的类、HttpRequestHandler接口实现类、Servlet类等),这些控制器的处理方法各不相同。HandlerAdapter作为适配器,将不同类型控制器的处理逻辑适配为统一接口,使DispatcherServlet(前端控制器)无需关心具体控制器类型。
1.1 HandlerAdapter接口(目标接口)
public interface HandlerAdapter {
// 判断当前适配器是否支持该处理器
boolean supports(Object handler);
// 处理请求,返回ModelAndView
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
// 获取最后修改时间
long getLastModified(HttpServletRequest request, Object handler);
}
1.2 具体处理器(被适配者)
Spring MVC支持的处理器类型:
@Controller注解的控制器:通过@RequestMapping定义处理方法HttpRequestHandler:实现handleRequest方法Servlet:继承HttpServlet,重写doGet/doPost方法
// @Controller注解的控制器
@Controller
public class AnnotationController {
@RequestMapping("/hello")
public ModelAndView hello() {
return new ModelAndView("hello");
}
}
// HttpRequestHandler实现类
public class HttpRequestController implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.getWriter().write("Hello World");
}
}
1.3 具体适配器实现
Spring MVC提供多种HandlerAdapter实现,适配不同类型的处理器:
// 适配@Controller注解的控制器
public class RequestMappingHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
// 支持带@RequestMapping的方法处理器
return handler instanceof HandlerMethod;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 调用@Controller中的@RequestMapping方法
return handleInternal(request, response, (HandlerMethod) handler);
}
// 内部处理逻辑...
}
// 适配HttpRequestHandler的适配器
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof HttpRequestHandler;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 调用HttpRequestHandler的handleRequest方法
((HttpRequestHandler) handler).handleRequest(request, response);
return null; // 无ModelAndView返回
}
}
1.4 DispatcherServlet中的适配逻辑
public class DispatcherServlet extends FrameworkServlet {
private List<HandlerAdapter> handlerAdapters;
@Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1. 获取处理器(如@Controller的方法)
Object handler = getHandler(request);
// 2. 获取支持该处理器的适配器
HandlerAdapter ha = getHandlerAdapter(handler);
// 3. 通过适配器调用处理器
ModelAndView mv = ha.handle(request, response, handler);
// 4. 渲染视图...
}
// 获取支持当前处理器的适配器
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler");
}
}
2. 适配器模式在Spring MVC中的价值
- 类型统一:将不同类型控制器的处理逻辑适配为统一接口,简化DispatcherServlet的设计
- 扩展灵活:新增控制器类型时,只需添加对应的HandlerAdapter,无需修改核心框架代码
- 职责清晰:DispatcherServlet专注于请求分发,适配器专注于接口转换,符合单一职责原则
- 兼容 legacy:支持适配旧类型控制器(如Servlet),保护现有代码投资
六、总结
1. 适配器模式的适用场景
- 当需要使用现有组件,但该组件的接口与系统要求的接口不兼容时
- 当需要集成多个接口不同的组件,且希望客户端以统一方式访问时
- 当需要创建可复用的组件,使其能够与未预见的接口协同工作时
- 当需要适配 legacy 系统,且不希望修改其源代码时
- 当需要在不破坏现有系统的前提下,为系统添加新功能时
2. 适配器模式与其他模式的区别
- 与装饰器模式:两者都通过包装对象增强功能,但装饰器模式不改变接口,仅扩展功能;适配器模式则专注于接口转换,使不兼容接口可协同工作。
- 与外观模式:两者都简化了接口,但外观模式为子系统提供统一入口,不强调接口转换;适配器模式则专注于解决接口不兼容问题,使客户端能按期望接口访问组件。
- 与桥接模式:两者都涉及接口适配,但桥接模式强调分离抽象与实现,支持两者独立变化;适配器模式则是在已有接口基础上进行转换,解决兼容性问题。
3. 支付系统中的实践价值
- 多渠道集成:快速对接不同支付渠道,屏蔽接口差异,降低集成成本
- 系统兼容性:实现新旧系统的平滑过渡,保护现有系统投资
- 功能复用:复用第三方组件(如风控引擎、清算系统),无需修改其代码
- 业务扩展:支持业务灵活扩展,新增功能时无需重构现有代码
- 合规适配:适配不同地区的支付监管要求(如欧盟PSD2、中国网联规范)
4. 实践建议
- 优先使用对象适配器(组合方式),避免类适配器的单继承限制
- 适配器应保持轻量,仅负责接口转换,不添加复杂业务逻辑
- 通过工厂模式管理适配器,支持动态选择和切换适配逻辑
- 为适配器编写完善的单元测试,重点测试边界情况(如格式错误、字段缺失)
- 避免过度使用适配器,当多个接口不兼容时,应考虑重构接口设计
适配器模式通过接口转换解决了系统异构问题,是支付框架中实现多渠道集成、系统兼容的核心设计模式。它既保护了现有系统的稳定性,又为未来扩展预留了空间,在支付系统的全生命周期中都发挥着重要作用。