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

前言

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

比如项目上线审批:

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

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

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

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


一、核心定义

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

它的核心思想是:

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

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

二、标准体系结构图

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

三、场景推演

假设公司规定如下:

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

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

员工只需要提交申请:

1
员工提交请假申请 → 组长 → 经理 → 总监

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

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

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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 大促审批流程升级规则:

1
2
3
4
5
6
7
8
任何时期:
└── 三级审批(王工 1000013)必须通过

2020-06-01 ~ 2020-06-25 期间,额外加:
└── 二级审批(张经理 1000012)必须通过

2020-06-11 ~ 2020-06-20 期间,再额外加:
└── 一级审批(段总 1000011)必须通过

面条代码的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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

责任链模式的解法:

1
2
3
4
5
6
7
8
9
10
11
链组装(只做一次):
Level3AuthLink(王工)→ Level2AuthLink(张经理)→ Level1AuthLink(段总)

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

4.2 架构图

4.2.1 面条代码架构图

image-20260525161123138

4.2.2 责任链模式架构图

image-20260525160359703


4.3 类图对比

4.3.1 面条代码类图

4.3.2 责任链模式类图


4.4 时序图

4.4.1 面条代码时序图

4.4.2 责任链模式时序图


4.5 代码分析

4.5.1 抽象链节点 AuthLink(责任链核心骨架)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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(链头,无时间限制,总是生效)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 期间生效)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 责任链组装与测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 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 策略模式:策略在一组算法中选择一个执行,责任链是顺序依次流过各节点