责任链模式

一、先明确:为什么考察责任链模式?

  1. 你是否能通过通俗例子理解责任链模式的核心思想,而非背枯燥定义?
  2. 能否掌握责任链模式的核心结构,并用 Java 代码实现简单的责任链?
  3. 能否关联实际项目中的应用(如 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)」,请求会沿着责任链按以下步骤处理:

  1. 请求进入链首节点: 调用 CarriageChainHandler.findCarriage(waybillDTO),请求从首节点 SameCityChainHandler 开始。
  2. 同城寄处理器处理: 判断北京≠上海(不同城),未找到模板(carriageEntity=null),通过 doNextHandler 传递给下一个处理器 SameProvinceChainHandler。
  3. **省内寄处理器处理:**通过 AreaFeign 查询北京(直辖市)和上海(直辖市)属于不同省份,未找到模板,传递给下一个处理器 EconomicZoneChainHandler。
  4. 经济区互寄处理器处理: 判断北京(京津冀)和上海(江浙沪)不属于同一经济区,未找到模板,传递给最后一个处理器 TransProvinceChainHandler。
  5. 跨省寄处理器处理: 默认匹配,查询跨省寄模板,返回模板对象(carriageEntity≠null)。
  6. **链终止:**由于已找到模板,doNextHandler 直接返回结果,不再传递,责任链处理结束。

最终,系统拿到跨省寄模板,用于后续运费计算。

七、应用优势

  1. 解耦请求发送者与接收者: 运费计算逻辑(CarriageServiceImpl.compute)只需调用 CarriageChainHandler.findCarriage,无需关心模板查找的具体顺序和处理器,发送者与接收者完全解耦。

  2. **符合开闭原则,扩展性极强:**若新增「跨境寄」模板,只需新增一个 CrossBorderChainHandler 类,实现 doHandler 逻辑并设置 @Order(500),无需修改原有处理器和链组装器代码。

  3. 逻辑清晰,职责单一: 每个处理器只负责一种模板类型的匹配逻辑,代码可读性高,便于维护(比如修改同城寄的匹配规则,只需修改 SameCityChainHandler)。

  4. **灵活调整顺序:**只需修改 @Order 注解的数值,即可调整模板查找的优先级(比如将经济区互寄的优先级高于省内寄,只需把 EconomicZoneChainHandler 的 @Order 改为 150)。

总结

该运费微服务中的责任链模式,完美贴合业务场景需求:通过「抽象处理器定义规范、具体处理器实现逻辑、链组装器构建顺序」,实现了「按优先级查找运费模板」的核心功能,同时保证了代码的解耦性、扩展性和可维护性。其本质是将「多条件优先级判断」转化为「链式节点处理」,是责任链模式在实际业务中典型且优雅的应用。

相关推荐
之歆1 天前
Spring AI入门到实战到原理源码-MCP
java·人工智能·spring
yangminlei1 天前
Spring Boot3集成LiteFlow!轻松实现业务流程编排
java·spring boot·后端
qq_318121591 天前
互联网大厂Java面试故事:从Spring Boot到微服务架构的技术挑战与解答
java·spring boot·redis·spring cloud·微服务·面试·内容社区
J_liaty1 天前
Spring Boot整合Nacos:从入门到精通
java·spring boot·后端·nacos
Mr__Miss1 天前
保持redis和数据库一致性(双写一致性)
数据库·redis·spring
阿蒙Amon1 天前
C#每日面试题-Array和ArrayList的区别
java·开发语言·c#
daidaidaiyu1 天前
Spring IOC 源码学习 一文学习完整的加载流程
java·spring
Knight_AL1 天前
Spring 事务传播行为 + 事务失效原因 + 传播行为为什么不用其他模式
数据库·sql·spring
2***d8851 天前
SpringBoot 集成 Activiti 7 工作流引擎
java·spring boot·后端
五阿哥永琪1 天前
Spring中的定时任务怎么用?
java·后端·spring