如何用责任链模式优雅的实现数据导入?
2019-04-24 本文已影响103人
苦行孙
责任链模式
标准的责任链模式
基本定义
- 创建多个处理对象,将其自由的搭配组装,组成一个处理器链条。
- 任务由此链条进行传递,当遇到合适的处理节点时,进行处理。
- 由某个节点处理完毕后,任务流程结束。
UML 类图
标准实现方式的 UML 类图标准模式所涉及到的角色:
- 抽象的处理器:负责定义抽象的 doHandle 处理方法, 以及存储下一个处理节点。
- 业务的处理器:业务的处理器:负责定义节点具体的业务逻辑。
优点
- 结构清晰,代码较为简单
缺点
- 不利于动态组合: next 处理节点,耦合在当前节点。
- 无法对象重用: 如果一个处理对象,需要组合到多种业务逻辑处理链条中,需要创建多个实例。
扩展:标准责任链模式,加入责任链的上下文管理对象
基本定义
- 在标准责任链的基础上,加入责任链的上下文管理对象。
- 处理节点的装配,由起始节点转移到了上下文管理对象。
UML 类图
加入责任链的上下文管理对象的 UML 类图标准模式所涉及到的角色:
- 责任链管理对象:负责存储实际的处理对象,并负责管理任务的执行流程(任务执行所需要的参数,由该对象负责存储)
- 抽象的处理器:负责定义抽象的 doHandle 方法。
- 业务的处理器:负责定义节点具体的业务逻辑。
优点
- 任务执行链的组合过程清晰。
- 不同业务场景下,可复用处理对象。
功能链
基本定义
- 任务由处理链的链头开始传递,每个处理节点处理任务的一部分功能。
- 由某个处理节点处理完后,继续向下执行。
- 一般情况下,当前节点的业务依赖于上一节点的数据处理结果。
UML 类图
类似于标准的责任链模式的扩展模式
常见场景
- Servlet 中的 Filter
- MyBatis 中的 Plugin
实际业务场景
最近工作中来了一个数据导入的需求,需要同时支持 xls, txt, csv 格式的业务数据导入,且不同文件的数据格式不一致,那么如何优雅的实现数据的导入呢?
功能分析
由于这几种格式,数据处理的业务逻辑一致,所以预期需要达到以下的三点:
- 将数据的读取、解析、转换和校验 的过程与业务处理流程分离。
- 将数据的读取、解析任务抽取出来,针对不同的数据格式进行不同的处理。
- 需要能够灵活的组装任务,已此达到将基础功能与业务解耦的目的。
综上分析,决定采用标准责任链的变种模式 —— 功能链
UML 类图
类图代码实现
由于篇幅有限,仅将关键代码贴出
功能链的上下文管理对象的抽象接口,负责构造处理器链。
public interface HandlerChain<T> {
/** 执行任务 */
void execute();
/** 获取上下文参数 */
T getContext();
/** 添加任务 */
HandlerChain<T> append(Handler handler);
/** 移除任务 */
HandlerChain<T> remove(Handler handler);
}
使用单向链表的方式实现上下文管理对象。
public class HandlerChainImpl<T> implements HandlerChain<T> {
private final List<Handler> handlers = new ArrayList<>();
/** 控制是否可以重复执行 */
private boolean cyclic;
/** 执行索引 */
private int executeIndex = 0;
@Getter
private T context;
@Override
public void execute() {
if (hasNext()) {
next().doHandler(this);
} else {
if (cyclic) {
executeIndex = 0;
}
}
}
private boolean hasNext() {
return executeIndex < handlers.size();
}
private Handler next() {
return handlers.get(executeIndex++);
}
}
定义抽象的处理器接口。
public interface Handler {
/**
* 执行任务
* @param chain 任务链
*/
void doTask(HandlerChain chain);
}
通过责任链的特性,优雅的实现了 Excel 文件流的打开与关闭。
public class ExcelReadAndCloseHandler implements Handler {
@Override
public void doHandler(HandlerChain chain) {
ExcelDataContext context = (ExcelDataContext) chain.getContext();
try (Workbook workbook = readExcel(context, context.getExcelWithBytes())) {
context.setWorkbook(workbook);
chain.execute();
} catch (IOException e) {
throw new UserDataImportException("导入失败:" + e.getMessage());
}
}
Excel 文件的数据读取,大体概述。
public class UserDataWithExcelReadHandler implements Handler {
@Override
public void doHandler(HandlerChain chain) {
UserImportDataContext context = (UserImportDataContext) chain.getContext();
// 概述,读取所有 sheet 表中的所有数据
for (int dataRowStartIndex = config.getDataRowStartIndex(); dataRowStartIndex <= sheet.getLastRowNum(); dataRowStartIndex++) {
Row next = sheet.getRow(dataRowStartIndex);
dataList.add(buildDataItem(cellHeadCellRow, next));
}
context.setDataList(dataList);
chain.execute();
}
}
数据导入的业务逻辑代码,大体概述。
注:该处理器为最后一步,为了保证后续能够灵活的添加新的任务,因此继续调用 chain.execute() 保证后续执行
public class DataGenerateHandler implements Handler {
@Override
public void doHandler(CustomTaskChain chain) {
UserImportDataContext context = (UserImportDataContext) chain.getContext();
List<UserDataImportBean> dataList = context.getDataList();
dataList.forEach(data -> {
generateUser(data)
generateJob(data);
generateRole(data);
});
chain.execute();
}
}
总结
本文主要介绍了行为模式中的责任链模式,将传统的 if...else...
语句,通过责任链模式进行分离,使得原来大块的臃肿代码,能够按照业务逻辑和功能重新组合执行,以此来降低耦合度,并提高代码的可复用性。
优点
- 可以提高代码的复用性,分离出的独立功能的处理对象,不涉及业务逻辑,因此可以自由的组合来复用该功能。
- 可以降低代码的耦合度。
- 可以灵活的动态组合功能。
缺点
- 将大量的
if
逻辑分离出之后,会产生大量细粒度对象。 - 标准的责任链模式下,可能会出现整个处理链传递完后,请求依然未被处理的情况。