责任链模式

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

  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)。

总结

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

相关推荐
@淡 定2 小时前
Java内存模型(JMM)详解
java·开发语言
czhc11400756632 小时前
C# 1221
java·servlet·c#
黄俊懿2 小时前
【深入理解SpringCloud微服务】Seata(AT模式)源码解析——全局事务的回滚
java·后端·spring·spring cloud·微服务·架构·架构师
派大鑫wink3 小时前
【Day12】String 类详解:不可变性、常用方法与字符串拼接优化
java·开发语言
JIngJaneIL3 小时前
基于springboot + vue健康管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·后端
秋饼3 小时前
【三大锁王争霸赛:Java锁、数据库锁、分布式锁谁是卷王?】
java·数据库·分布式
电商API&Tina3 小时前
【电商API接口】关于电商数据采集相关行业
java·python·oracle·django·sqlite·json·php
刘一说3 小时前
Spring Boot中IoC(控制反转)深度解析:从实现机制到项目实战
java·spring boot·后端
悟空码字3 小时前
SpringBoot参数配置:一场“我说了算”的奇幻之旅
java·spring boot·后端