设计模式 · 责任链模式(Chain of Responsibility Pattern)

文章目录

    • 前言
    • 一、核心定义
    • 二、标准体系结构图
    • 三、场景推演
    • 四、实战案例
      • [4.1 需求分析](#4.1 需求分析)
      • [4.2 架构图](#4.2 架构图)
        • [4.2.1 面条代码架构图](#4.2.1 面条代码架构图)
        • [4.2.2 责任链模式架构图](#4.2.2 责任链模式架构图)
      • [4.3 类图对比](#4.3 类图对比)
        • [4.3.1 面条代码类图](#4.3.1 面条代码类图)
        • [4.3.2 责任链模式类图](#4.3.2 责任链模式类图)
      • [4.4 时序图](#4.4 时序图)
        • [4.4.1 面条代码时序图](#4.4.1 面条代码时序图)
        • [4.4.2 责任链模式时序图](#4.4.2 责任链模式时序图)
      • [4.5 代码分析](#4.5 代码分析)
        • [4.5.1 抽象链节点 `AuthLink`(责任链核心骨架)](#4.5.1 抽象链节点 AuthLink(责任链核心骨架))
        • [4.5.2 三级节点 `Level3AuthLink`(链头,无时间限制,总是生效)](#4.5.2 三级节点 Level3AuthLink(链头,无时间限制,总是生效))
        • [4.5.3 二级节点 `Level2AuthLink`(仅 6.1~6.25 期间生效)](#4.5.3 二级节点 Level2AuthLink(仅 6.1~6.25 期间生效))
        • [4.5.4 责任链组装与测试](#4.5.4 责任链组装与测试)
    • 总结

前言

在业务系统中,经常会遇到"多级审批""多层校验""多步骤过滤"这类流程。

比如项目上线审批:

  • 平时只需要三级负责人审批;
  • 618 大促期间,需要额外经过二级负责人审批;
  • 618 核心时间段,还需要一级负责人审批;
  • 每一级审批通过后,才可以继续向下流转。

如果直接用 if-else 写,代码很快就会变成一大坨判断逻辑。新增审批级别、调整审批顺序、修改审批规则,都需要改原来的核心方法。

责任链模式的作用,就是把这些审批环节拆成一个个独立节点,再用 next 把它们串成一条链。

本文代码链接:https://github.com/likerhood/CodeDesignWork/tree/main/codedesign12.0-0


一、核心定义

责任链模式(Chain of Responsibility Pattern) 是一种行为型设计模式,将请求的发送者和接收者解耦,让多个对象都有机会处理请求,将这些对象串成一条链,请求沿链传递,直到有对象处理它为止。

它的核心思想是:

将多个处理对象串成一条链,请求从链头开始传递,每个节点决定自己是否处理,以及是否继续传给下一个节点。

  • 解决问题:多级审批、多层过滤、多步骤验证等"流程链"场景中,if-else 造成的强耦合与难扩展
  • 关键特征
    • 每个节点实现同一抽象类/接口,决定自己处理还是传给下一个
    • 链的组装在运行时进行,可动态调整
    • 调用方只持有链头引用,不感知链的内部结构

二、标准体系结构图

继承
继承
继承
next(链接下一个)
只持有链头引用
<<abstract>>
Handler
-next: Handler
+setNext(handler: Handler) : Handler
+handle(request: Request) : Response
ConcreteHandler1
+handle(request: Request) : Response
ConcreteHandler2
+handle(request: Request) : Response
ConcreteHandler3
+handle(request: Request) : Response
Client

角色 作用 报销审批中的对应对象
抽象处理者 Handler 定义处理方法,并持有下一个节点 抽象审批人
具体处理者 ConcreteHandler 判断自己是否能处理请求 组长、经理、总监、CFO
客户端 Client 组装链条,并发起请求 报销服务或系统启动配置

三、场景推演

假设公司规定如下:

请假天数 审批人
1 天以内 组长
3 天以内 经理
超过 3 天 总监

员工提交请假申请后,不需要自己判断应该找组长、经理还是总监。

员工只需要提交申请:

复制代码
员工提交请假申请 → 组长 → 经理 → 总监

每个审批人收到申请后,只判断一件事:

当前请假天数是否在我的审批范围内?

  • 如果在自己的审批范围内,就审批通过;
  • 如果超出自己的权限,就交给下一个审批人。

代码:

java 复制代码
public class LeaveApprovalChainDemo {

    /**
     * 请求对象:员工提交的请假申请。
     */
    static class LeaveRequest {
        private final String employeeName;
        private final int days;
        private final String reason;

        public LeaveRequest(String employeeName, int days, String reason) {
            if (days <= 0) {
                throw new IllegalArgumentException("请假天数必须大于 0");
            }
            this.employeeName = employeeName;
            this.days = days;
            this.reason = reason;
        }

        public String getEmployeeName() {
            return employeeName;
        }

        public int getDays() {
            return days;
        }

        public String getReason() {
            return reason;
        }
    }

    /**
     * 抽象处理者:审批人。
     *
     * 每个审批人都保存下一个审批人。
     * 当前审批人能够处理,就直接审批;
     * 当前审批人不能处理,就将申请交给下一个审批人。
     */
    static abstract class Approver {
        private Approver next;

        /**
         * 设置下一个审批人。
         */
        public Approver setNext(Approver next) {
            this.next = next;
            return next;
        }

        /**
         * 处理请假申请。
         */
        public final void handle(LeaveRequest request) {
            System.out.printf(
                    "%s查看申请:%s请假%d天,原因:%s%n",
                    getRole(),
                    request.getEmployeeName(),
                    request.getDays(),
                    request.getReason()
            );

            if (canApprove(request)) {
                System.out.printf(
                        "%s审批通过:%s请假%d天%n",
                        getRole(),
                        request.getEmployeeName(),
                        request.getDays()
                );
                return;
            }

            if (next != null) {
                System.out.printf(
                        "%s权限不足,转交给%s%n",
                        getRole(),
                        next.getRole()
                );
                next.handle(request);
            } else {
                System.out.println("当前申请没有审批人可以处理");
            }
        }

        /**
         * 判断当前审批人能否处理这张请假申请。
         */
        protected abstract boolean canApprove(LeaveRequest request);

        /**
         * 获取当前审批人的角色名称。
         */
        protected abstract String getRole();
    }

    /**
     * 具体处理者一:组长。
     *
     * 组长最多审批 1 天请假。
     */
    static class TeamLeader extends Approver {

        @Override
        protected boolean canApprove(LeaveRequest request) {
            return request.getDays() <= 1;
        }

        @Override
        protected String getRole() {
            return "组长";
        }
    }

    /**
     * 具体处理者二:经理。
     *
     * 经理最多审批 3 天请假。
     */
    static class Manager extends Approver {

        @Override
        protected boolean canApprove(LeaveRequest request) {
            return request.getDays() <= 3;
        }

        @Override
        protected String getRole() {
            return "经理";
        }
    }

    /**
     * 具体处理者三:总监。
     *
     * 总监作为链条最后一个节点,处理超过 3 天的请假。
     */
    static class Director extends Approver {

        @Override
        protected boolean canApprove(LeaveRequest request) {
            return true;
        }

        @Override
        protected String getRole() {
            return "总监";
        }
    }

    public static void main(String[] args) {

        /*
         * 组装责任链:
         * 组长 -> 经理 -> 总监
         */
        Approver chain = new TeamLeader();

        chain.setNext(new Manager())
             .setNext(new Director());

        LeaveRequest request1 = new LeaveRequest(
                "小王",
                1,
                "家中有事"
        );

        LeaveRequest request2 = new LeaveRequest(
                "小李",
                2,
                "外出办事"
        );

        LeaveRequest request3 = new LeaveRequest(
                "小张",
                5,
                "回家探亲"
        );

        System.out.println("===== 第一张请假申请 =====");
        chain.handle(request1);

        System.out.println("\n===== 第二张请假申请 =====");
        chain.handle(request2);

        System.out.println("\n===== 第三张请假申请 =====");
        chain.handle(request3);
    }
}

四、实战案例

4.1 需求分析

618 大促审批流程升级规则:

复制代码
任何时期:
  └── 三级审批(王工 1000013)必须通过
  
2020-06-01 ~ 2020-06-25 期间,额外加:
  └── 二级审批(张经理 1000012)必须通过
  
2020-06-11 ~ 2020-06-20 期间,再额外加:
  └── 一级审批(段总 1000011)必须通过

面条代码的问题:

java 复制代码
// if 嵌套地狱,增加一级审批 = 新增一坨 if
Date date = AuthService.queryAuthInfo("1000013", orderId);
if (null == date) return ... 王工;

if (日期 in 6.1~6.25) {
    date = AuthService.queryAuthInfo("1000012", orderId);
    if (null == date) return ... 张经理;
}
if (日期 in 6.11~6.20) {
    date = AuthService.queryAuthInfo("1000011", orderId);
    if (null == date) return ... 段总;
}
return 审批完成;

问题:

  • 所有审批逻辑堆在一个方法,职责混乱
  • 新增审批级别 → 修改原有 if 逻辑,违反开闭原则
  • 不同时期的审批规则变化 → 难以维护,改动容易引入 bug

责任链模式的解法:

复制代码
链组装(只做一次):
Level3AuthLink(王工)→ Level2AuthLink(张经理)→ Level1AuthLink(段总)

调用方:
authLink.doAuth(uId, orderId, authDate)
    └── Level3AuthLink.doAuth()  检查三级是否通过?
              ├── 未通过 → 返回"待三级审批"
              └── 通过 → 检查时间是否在 6.1~6.25?
                          ├── 否 → 返回完成(不需要二级审批)
                          └── 是 → next.doAuth()(传给 Level2)
                                    └── Level2AuthLink.doAuth() ...

4.2 架构图

4.2.1 面条代码架构图
4.2.2 责任链模式架构图

4.3 类图对比

4.3.1 面条代码类图

多次硬编码调用
创建并返回
AuthController
-f: SimpleDateFormat
+doAuth(uId: String, orderId: String, authDate: Date) : AuthInfo
<<static 审批记录>>
AuthService
+queryAuthInfo(uId: String, orderId: String) : Date
+auth(uId: String, orderId: String) : void
AuthInfo
-code: String
-info: String
三级审批逻辑全部堆在\ndoAuth() 一个方法中\n含日期判断 if-else

4.3.2 责任链模式类图

next(链接下一节点)
<<abstract 抽象链节点>>
AuthLink
#levelUserId: String
#levelUserName: String
#next: AuthLink
+getNext() : AuthLink
+appendNext(next: AuthLink) : AuthLink
+doAuth(uId: String, orderId: String, authDate: Date) : AuthInfo
<<三级负责人(王工)>>
Level3AuthLink
+doAuth(...) : AuthInfo
<<二级负责人(张经理)>>
Level2AuthLink
-beginDate: Date
-endDate: Date
+doAuth(...) : AuthInfo
<<一级负责人(段总)>>
Level1AuthLink
-beginDate: Date
-endDate: Date
+doAuth(...) : AuthInfo
AuthService
-authMap: Map<String, Date>
+queryAuthInfo(uId: String, orderId: String) : Date
+auth(uId: String, orderId: String) : void
AuthInfo
-code: String
-info: String
核心:持有 next 引用\nappendNext() 构建链\ndoAuth() 抽象方法由子类实现


4.4 时序图

4.4.1 面条代码时序图

AuthService AuthController ApiTest AuthService AuthController ApiTest doAuth("小傅哥", "1000998...", date) queryAuthInfo("1000013", orderId) null 待三级审批(王工) auth("1000013", orderId) [王工审批] doAuth(...) queryAuthInfo("1000013", orderId) → 有时间 queryAuthInfo("1000012", orderId) null 待二级审批(张经理) auth("1000012", orderId) [张经理审批] doAuth(...) queryAuthInfo("1000013") → 有 queryAuthInfo("1000012") → 有 queryAuthInfo("1000011", orderId) null 待一级审批(段总) auth("1000011", orderId) [段总审批] doAuth(...) 三次查询全部有时间记录 审批完成

4.4.2 责任链模式时序图

AuthService Level1AuthLink(段总) Level2AuthLink(张经理) Level3AuthLink(王工) ApiTest AuthService Level1AuthLink(段总) Level2AuthLink(张经理) Level3AuthLink(王工) ApiTest 组装链头:L3→L2→L1 next != null,传递 时间在 6.1~6.25,传递给 L1 next == null authLink.doAuth("小傅哥", orderId, date) queryAuthInfo("1000013", orderId) → null 0001:待三级审批(王工) auth("1000013", orderId) authLink.doAuth(...) queryAuthInfo("1000013") → 有记录 next.doAuth(...) queryAuthInfo("1000012", orderId) → null 0001:待二级审批(张经理) auth("1000012", orderId) authLink.doAuth(...) queryAuthInfo("1000013") → 有 next.doAuth(...) queryAuthInfo("1000012") → 有 next.doAuth(...) queryAuthInfo("1000011", orderId) → null 0001:待一级审批(段总) auth("1000011", orderId) authLink.doAuth(...) 传递 传递 queryAuthInfo("1000011") → 有记录 0000:审批完成(段总,时间:...)


4.5 代码分析

4.5.1 抽象链节点 AuthLink(责任链核心骨架)
java 复制代码
// AuthLink.java
public abstract class AuthLink {
    protected String levelUserId;       // 当前节点的审批人ID
    protected String levelUserName;     // 当前节点的审批人姓名
    protected AuthLink next;            // 下一个节点(链接)

    public AuthLink(String levelUserId, String levelUserName) {
        this.levelUserId = levelUserId;
        this.levelUserName = levelUserName;
    }

    public AuthLink getNext() { return next; }

    // ✅ 核心:appendNext() 支持链式调用组装责任链
    // new Level3().appendNext(new Level2().appendNext(new Level1()))
    public AuthLink appendNext(AuthLink next) {
        this.next = next;
        return this;  // 返回 this,支持链式调用
    }

    // 抽象方法:每个具体节点实现自己的审批逻辑
    public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);
}

4.5.2 三级节点 Level3AuthLink(链头,无时间限制,总是生效)
java 复制代码
public class Level3AuthLink extends AuthLink {

    public Level3AuthLink(String levelUserId, String levelUserName) {
        super(levelUserId, levelUserName);
    }

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        // ① 查询三级审批是否已完成
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            // ② 未审批 → 返回"待审批",请求在此节点终止
            return new AuthInfo("0001", "单号:", orderId, " 状态:待三级审批负责人 ", levelUserName);
        }
        AuthLink next = super.getNext();
        if (null == next) {
            // ③ 已审批 + 无下一节点 → 链路结束,返回完成
            return new AuthInfo("0000", "单号:", orderId, " 状态:一级审批完成负责人",
                " 时间:", f.format(date), " 审批人:", levelUserName);
        }
        // ④ 已审批 + 有下一节点 → 传递给下一节点继续处理
        return next.doAuth(uId, orderId, authDate);
    }
}

4.5.3 二级节点 Level2AuthLink(仅 6.1~6.25 期间生效)
java 复制代码
public class Level2AuthLink extends AuthLink {

    // ✅ 关键:时间判断封装在节点内部,不暴露给外部
    private Date beginDate = f.parse("2020-06-01 00:00:00");
    private Date endDate   = f.parse("2020-06-25 23:59:59");

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "单号:", orderId, " 状态:待二级审批负责人 ", levelUserName);
        }
        AuthLink next = super.getNext();
        if (null == next) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人",
                " 时间:", f.format(date), " 审批人:", levelUserName);
        }
        // ✅ 不在大促时间段 → 不需要再往上审批,直接返回完成
        if (authDate.before(beginDate) || authDate.after(endDate)) {
            return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人",
                " 时间:", f.format(date), " 审批人:", levelUserName);
        }
        return next.doAuth(uId, orderId, authDate);
    }
}

4.5.4 责任链组装与测试
java 复制代码
// ApiTest.java
@Test
public void test_AuthLink() throws ParseException {
    // ✅ 责任链组装:Level3(王工)→ Level2(张经理)→ Level1(段总)
    AuthLink authLink = new Level3AuthLink("1000013", "王工")
            .appendNext(new Level2AuthLink("1000012", "张经理")
                    .appendNext(new Level1AuthLink("1000011", "段总")));

    // 查询当前状态(三级未审批)
    logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));

    // 模拟三级审批
    AuthService.auth("1000013", "1000998004813441");
    logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));

    // 模拟二级审批
    AuthService.auth("1000012", "1000998004813441");
    logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));

    // 模拟一级审批
    AuthService.auth("1000011", "1000998004813441");
    logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));
}

// 输出:
// {"code":"0001","info":"单号:1000998... 状态:待三级审批负责人 王工"}
// {"code":"0001","info":"单号:1000998... 状态:待二级审批负责人 张经理"}
// {"code":"0001","info":"单号:1000998... 状态:待一级审批负责人 段总"}
// {"code":"0000","info":"单号:1000998... 状态:一级审批完成负责人 时间:... 审批人:段总"}

总结

维度 面条代码 责任链模式
审批逻辑位置 全部堆在 doAuth() 一个方法 每级封装在各自节点类中
新增审批级别 修改原有 if 逻辑,影响稳定性 新建节点类,appendNext() 插入链中
审批顺序调整 重写 if 逻辑 修改链的组装顺序即可
时间判断逻辑 散落在 if 条件中 封装在对应节点内部
符合开闭原则 否(修改原有代码) 是(扩展新增,不改旧代码)

责任链模式的关键实现要点:

  1. appendNext() 返回 this:支持链式调用,是优雅组装责任链的语法糖。
  2. 每个节点的三判断模式 :① 当前节点是否满足处理条件?不满足 → 提前返回。② 有无下一节点?无 → 返回最终结果。③ 传递给下一节点 next.doAuth()
  3. 链的可配置化:本案例是代码硬组装,生产中常见的方式是从数据库/配置文件读取审批链配置,动态组装,实现完全可配置的流程引擎。

责任链模式的适用场景:

场景 说明
多级审批流程 报销审批、上线审批、权限申请等
HTTP 过滤器/拦截器链 javax.servlet.Filter 就是责任链
Spring Security 过滤器链 多个 SecurityFilter 串成链
Netty ChannelPipeline 入站/出站处理器链
日志级别过滤 多个 Logger 节点判断日志级别

与其他模式对比:

  • 责任链模式 vs 装饰器模式 :装饰器的每个节点都会 执行(叠加功能),责任链的节点可以中断链路(提前返回)
  • 责任链模式 vs 组合模式:组合模式是树形结构,可双向遍历;责任链是线性结构,单向传递
  • 责任链模式 vs 策略模式 :策略在一组算法中选择一个 执行,责任链是顺序依次流过各节点
相关推荐
追烽少年x16 小时前
STL中的设计模式(一)
c++·设计模式
乐观的山里娃17 小时前
【设计模式 10】抽象工厂:整体换季
设计模式
贵慜_Derek19 小时前
《从零实现 Agent 系统》连载 08|编排与工作流:从 Chat 到任务图
人工智能·设计模式·架构
W.W.H.19 小时前
C++ 设计模式:6 个常用模式的实战示例
开发语言·c++·设计模式
老码观察20 小时前
设计模式实战解读(三):模板方法模式——骨架复用与扩展点设计
设计模式·模板方法模式
c++之路20 小时前
责任链模式(Chain of Responsibility Pattern)
java·前端·责任链模式
雪度娃娃20 小时前
行为型设计模式——状态模式
ui·设计模式·状态模式
折哥的程序人生 · 物流技术专研20 小时前
Java 23 种设计模式:从踩坑到精通 —— 开篇及系列介绍
java·开发语言·后端·设计模式·面试·架构
蜡笔小马20 小时前
15.C++设计模式-观察者模式
c++·观察者模式·设计模式