一、先明确:为什么考察责任链模式?
- 你是否能通过通俗例子理解责任链模式的核心思想,而非背枯燥定义?
- 能否掌握责任链模式的核心结构,并用 Java 代码实现简单的责任链?
- 能否关联实际项目中的应用(如 Spring 拦截器、Servlet 过滤器),体现理论结合实践的能力?
二、先铺垫:什么是责任链模式?
1. 通俗定义
责任链模式(Chain of Responsibility Pattern)是指:将多个处理者连成一条链式结构,当一个请求到来时,请求会沿着这条链依次传递,直到有一个处理者能处理该请求,或遍历完所有处理者为止。每个处理者只负责自己职责范围内的请求,超出范围则传递给下一个处理者,实现 "请求与处理解耦"。
2. 生活类比
最典型的例子是请假审批流程:
- 员工请假 1 天:组长即可审批(处理请求),无需传递给上级;
- 员工请假 3 天:组长无权处理,自动传递给经理审批;
- 员工请假 7 天:经理无权处理,自动传递给总监审批;
- 员工请假 15 天:总监也无权处理,传递到总经理审批(或最终无人处理,返回审批失败)。
这里的 "组长→经理→总监→总经理" 就是一条责任链,每个角色是 "处理者",请假天数是 "请求信息",处理者根据自身权限决定是处理请求还是传递给下一个。
三、核心:责任链模式的 3 大角色
| 角色名称 | 核心职责 | 对应请假场景示例 |
|---|---|---|
| 抽象处理者(Handler) | 1. 定义统一的请求处理方法;2. 持有下一个处理者的引用(形成链式结构);3. 声明传递请求的逻辑 | AbstractApprover(抽象审批者) |
| 具体处理者(ConcreteHandler) | 1. 实现抽象处理者的处理方法;2. 根据自身能力判断是否处理请求;3. 无法处理则传递给下一个处理者 | GroupLeader(组长)、Manager(经理)、Director(总监) |
| 请求对象(Request) | 封装请求的相关信息(如请求类型、请求参数等),供处理者判断和使用 | LeaveRequest(请假请求,包含请假人、天数、理由) |
四、项目应用:为什么要用责任链模式?
运费模板的查找存在明确的优先级规则:同城寄 > 省内寄 > 经济区互寄 > 跨省寄。
- 必须按此顺序依次判断:先检查是否为同城,若否再检查是否为省内,以此类推,找到第一个匹配的模板后立即停止(无需后续判断);
- 未来可能扩展新模板类型(如「跨境寄」),需保证扩展时不修改原有逻辑(符合「开闭原则」)。
若用传统的 if-else 逻辑,会导致代码耦合严重、扩展性差(新增类型需修改判断条件)。而责任链模式通过「链式结构」和「处理器分工」,完美解决了这两个问题。
五、项目中责任链模式的核心组件
责任链模式的核心是「抽象处理器 + 具体处理器 + 链组装器」,对应实现如下:
1. 抽象处理器:定义链节点的统一规范
抽象类 AbstractCarriageChainHandler 是责任链的「骨架」,定义了所有处理器的统一接口和链传递规则:
public abstract class AbstractCarriageChainHandler {
// 下一个处理器(链的节点关联)
private AbstractCarriageChainHandler nextHandler;
// 抽象处理方法:子类必须实现自己的匹配逻辑
public abstract CarriageEntity doHandler(WaybillDTO waybillDTO);
// 传递请求到下一个处理器(核心传递逻辑)
protected CarriageEntity doNextHandler(WaybillDTO waybillDTO, CarriageEntity carriageEntity) {
// 终止条件:没有下一个处理器,或当前已找到匹配模板(carriageEntity != null)
if (nextHandler == null || carriageEntity != null) {
return carriageEntity;
}
// 否则传递给下一个处理器
return nextHandler.doHandler(waybillDTO);
}
// 设置下一个处理器(用于组装链)
public void setNextHandler(AbstractCarriageChainHandler nextHandler) {
this.nextHandler = nextHandler;
}
}
- 核心作用:封装「链的传递规则」(doNextHandler)和「节点关联方式」(nextHandler),子类只需专注于自己的「匹配逻辑」(doHandler)。
2. 具体处理器:实现各自的匹配逻辑
4 种模板类型分别实现了具体处理器,每个处理器只负责判断「当前请求是否符合自己的模板类型」,并通过 @Order 注解定义优先级:
| 具体处理器类 | 功能(匹配逻辑) | @Order 优先级(越小越先执行) |
|---|---|---|
SameCityChainHandler |
判断收发城市是否相同(同城寄) | 100 |
SameProvinceChainHandler |
判断收发城市是否同省(省内寄) | 200 |
EconomicZoneChainHandler |
判断收发城市是否属于同一经济区(经济区互寄) | 300 |
TransProvinceChainHandler |
默认处理(跨省寄,无匹配时最后执行) | 400 |
以「同城寄处理器」为例,其实现逻辑:
@Component
@Order(100) // 优先级最高
public class SameCityChainHandler extends AbstractCarriageChainHandler {
@Resource
private CarriageService carriageService;
@Override
public CarriageEntity doHandler(WaybillDTO waybillDTO) {
CarriageEntity carriageEntity = null;
// 匹配逻辑:判断收发城市ID是否相同
if (ObjectUtil.equals(waybillDTO.getReceiverCityId(), waybillDTO.getSenderCityId())) {
// 匹配成功:查询同城寄模板
carriageEntity = carriageService.findByTemplateType(CarriageConstant.SAME_CITY);
}
// 传递给下一个处理器(若已找到模板,下一个处理器不会再处理)
return doNextHandler(waybillDTO, carriageEntity);
}
}
- 每个具体处理器的核心逻辑:「判断是否匹配」→「匹配则查询模板」→「传递给下一个处理器」。
3. 链组装器:构建完整的责任链
CarriageChainHandler 类负责将所有具体处理器按 @Order 顺序组装成链:
@Component
public class CarriageChainHandler {
// Spring自动注入所有AbstractCarriageChainHandler实现类,并按@Order排序
@Resource
private List<AbstractCarriageChainHandler> chainHandlers;
// 链的首节点
private AbstractCarriageChainHandler firstHandler;
// 初始化时组装链(@PostConstruct:Spring实例化后执行)
@PostConstruct
private void constructChain() {
if (CollUtil.isEmpty(chainHandlers)) {
throw new SLException("未找到运费模板处理器");
}
// 第一个处理器作为首节点
firstHandler = chainHandlers.get(0);
// 依次设置每个处理器的下一个节点
for (int i = 0; i < chainHandlers.size(); i++) {
if (i == chainHandlers.size() - 1) {
// 最后一个处理器:下一个节点为null(链的终点)
chainHandlers.get(i).setNextHandler(null);
} else {
// 当前处理器的下一个节点 = 下一个处理器
chainHandlers.get(i).setNextHandler(chainHandlers.get(i + 1));
}
}
}
// 对外提供查询入口:从首节点开始处理请求
public CarriageEntity findCarriage(WaybillDTO waybillDTO) {
return firstHandler.doHandler(waybillDTO);
}
}
- 关键逻辑:利用 Spring 的依赖注入特性,自动收集所有具体处理器并按 @Order 排序,再通过循环设置每个节点的 nextHandler,构建出「同城→省内→经济区→跨省」的完整责任链。
六、工作流程
假设用户下单:「从北京(senderCityId=2)寄到上海(receiverCityId=161793)」,请求会沿着责任链按以下步骤处理:
- 请求进入链首节点: 调用 CarriageChainHandler.findCarriage(waybillDTO),请求从首节点 SameCityChainHandler 开始。
- 同城寄处理器处理: 判断北京≠上海(不同城),未找到模板(carriageEntity=null),通过 doNextHandler 传递给下一个处理器 SameProvinceChainHandler。
- **省内寄处理器处理:**通过 AreaFeign 查询北京(直辖市)和上海(直辖市)属于不同省份,未找到模板,传递给下一个处理器 EconomicZoneChainHandler。
- 经济区互寄处理器处理: 判断北京(京津冀)和上海(江浙沪)不属于同一经济区,未找到模板,传递给最后一个处理器 TransProvinceChainHandler。
- 跨省寄处理器处理: 默认匹配,查询跨省寄模板,返回模板对象(carriageEntity≠null)。
- **链终止:**由于已找到模板,doNextHandler 直接返回结果,不再传递,责任链处理结束。
最终,系统拿到跨省寄模板,用于后续运费计算。
七、应用优势
-
解耦请求发送者与接收者: 运费计算逻辑(CarriageServiceImpl.compute)只需调用 CarriageChainHandler.findCarriage,无需关心模板查找的具体顺序和处理器,发送者与接收者完全解耦。
-
**符合开闭原则,扩展性极强:**若新增「跨境寄」模板,只需新增一个 CrossBorderChainHandler 类,实现 doHandler 逻辑并设置 @Order(500),无需修改原有处理器和链组装器代码。
-
逻辑清晰,职责单一: 每个处理器只负责一种模板类型的匹配逻辑,代码可读性高,便于维护(比如修改同城寄的匹配规则,只需修改 SameCityChainHandler)。
-
**灵活调整顺序:**只需修改 @Order 注解的数值,即可调整模板查找的优先级(比如将经济区互寄的优先级高于省内寄,只需把 EconomicZoneChainHandler 的 @Order 改为 150)。
总结
该运费微服务中的责任链模式,完美贴合业务场景需求:通过「抽象处理器定义规范、具体处理器实现逻辑、链组装器构建顺序」,实现了「按优先级查找运费模板」的核心功能,同时保证了代码的解耦性、扩展性和可维护性。其本质是将「多条件优先级判断」转化为「链式节点处理」,是责任链模式在实际业务中典型且优雅的应用。