责任链模式:审批流程——让每个节点都有机会说“不”
大家好,我是你们的老朋友。今天我们来聊聊责任链模式。
说实话,这个模式在业务系统里太常见了。你想想看,但凡涉及到“逐级审批”、“层层过滤”的场景,十有八九就是责任链的用武之地。我最早接触它,是在做一个OA系统的请假模块。那时候刚入行,代码写得那叫一个“直来直去”——if-else 嵌套了七八层,组长审批完判断是否要经理审批,经理审批完再判断是否要总监审批……后来代码改得我头皮发麻,才意识到:嗯,该用责任链了。
什么是责任链模式?
责任链模式,说白了就是:把多个处理对象串成一条链,请求沿着链传递,直到有对象处理它为止。
它的核心思想是:解耦请求的发送者和接收者。发送者不需要知道谁会处理这个请求,也不需要知道处理顺序。它只管把请求丢出去,剩下的交给链上的节点。
核心角色:
- Handler(抽象处理者):定义处理请求的接口,并持有下一个处理者的引用。
- ConcreteHandler(具体处理者):实现自己的处理逻辑,如果自己能处理就处理,否则交给下一个。
- Client(客户端):组装链,并触发请求。
业务场景:OA系统中的请假审批
咱们直接上场景。假设公司请假审批规则是这样的:
- 请假天数 ≤ 3 天:组长审批即可
- 3 天 < 天数 ≤ 7 天:组长审批后,需要经理审批
- 7 天 < 天数 ≤ 15 天:组长 → 经理 → 总监
- 天数 > 15 天:组长 → 经理 → 总监 → CEO
你看,这就是一条典型的责任链。每个节点都有机会处理,处理不了就往后传。
代码实现
我习惯先定义一个抽象的处理者。注意,这里要持有下一个节点的引用:
// 抽象处理者
public abstract class Approver {
protected Approver nextApprover; // 下一个审批人
public void setNextApprover(Approver nextApprover) {
this.nextApprover = nextApprover;
}
// 模板方法:处理请求
public void handleRequest(LeaveRequest request) {
if (canApprove(request)) {
doApprove(request);
} else if (nextApprover != null) {
nextApprover.handleRequest(request);
} else {
System.out.println("无人能审批,请求被拒绝");
}
}
protected abstract boolean canApprove(LeaveRequest request);
protected abstract void doApprove(LeaveRequest request);
}
然后定义具体的审批人。每个节点只关心自己能处理的范围:
// 组长
public class GroupLeader extends Approver {
@Override
protected boolean canApprove(LeaveRequest request) {
return request.getDays() <= 3;
}
@Override
protected void doApprove(LeaveRequest request) {
System.out.println("组长审批通过:" + request.getName() + " 请假 " + request.getDays() + " 天");
}
}
// 经理
public class Manager extends Approver {
@Override
protected boolean canApprove(LeaveRequest request) {
return request.getDays() <= 7;
}
@Override
protected void doApprove(LeaveRequest request) {
System.out.println("经理审批通过:" + request.getName() + " 请假 " + request.getDays() + " 天");
}
}
// 总监
public class Director extends Approver {
@Override
protected boolean canApprove(LeaveRequest request) {
return request.getDays() <= 15;
}
@Override
protected void doApprove(LeaveRequest request) {
System.out.println("总监审批通过:" + request.getName() + " 请假 " + request.getDays() + " 天");
}
}
// CEO
public class CEO extends Approver {
@Override
protected boolean canApprove(LeaveRequest request) {
return true; // CEO 啥都能批
}
@Override
protected void doApprove(LeaveRequest request) {
System.out.println("CEO审批通过:" + request.getName() + " 请假 " + request.getDays() + " 天");
}
}
客户端组装链,发起请求:
public class Client {
public static void main(String[] args) {
// 组装责任链
Approver groupLeader = new GroupLeader();
Approver manager = new Manager();
Approver director = new Director();
Approver ceo = new CEO();
groupLeader.setNextApprover(manager);
manager.setNextApprover(director);
director.setNextApprover(ceo);
// 发起请求
LeaveRequest request = new LeaveRequest("张三", 10);
groupLeader.handleRequest(request);
}
}
输出结果:
组长审批通过:张三 请假 10 天
经理审批通过:张三 请假 10 天
总监审批通过:张三 请假 10 天
等等,这里有个问题。你看输出,组长、经理、总监都审批了?这不对啊!
嗯,这里要特别注意。我上面的代码是每个节点都处理了,但实际业务中,一个请求通常只被一个节点处理。比如请假10天,组长审批完,应该直接交给经理,组长自己不应该“通过”。
所以正确的做法是:每个节点处理完自己的部分后,如果还需要上级审批,就继续往下传;如果不需要了,就终止。或者更常见的做法是:每个节点只判断自己能不能批,能批就批,不能批就传,而不是“批了还传”。
避坑指南: 我曾经在一个项目中,把责任链写成了“每个节点都处理一遍”,结果一个请假单被四个人审批了四次。后来才发现,责任链的典型用法是“找到一个能处理的就停止”,除非你的业务明确要求“逐级审批且每级都要签字”。
所以,正确的逻辑应该是:如果当前节点能处理,就处理并结束;否则传给下一个。修改一下 handleRequest 方法:
public void handleRequest(LeaveRequest request) {
if (canApprove(request)) {
doApprove(request);
} else if (nextApprover != null) {
nextApprover.handleRequest(request);
} else {
System.out.println("无人能审批,请求被拒绝");
}
}
这样,请假10天,组长不能批(因为超过3天),传给经理;经理不能批(超过7天),传给总监;总监能批(≤15天),审批通过,结束。
另一个经典场景:Java Web 中的 Filter 链
说到责任链,Java Web 开发者肯定不陌生。Servlet 中的 Filter 就是典型的责任链模式。
你想想看,一个请求进来,经过多个 Filter:
- 第一个 Filter 做字符编码处理
- 第二个 Filter 做登录校验
- 第三个 Filter 做日志记录
- ……
每个 Filter 处理完自己的逻辑后,调用 chain.doFilter() 把请求传给下一个 Filter。这就是责任链的变体——每个节点都处理,但处理完后继续传递。
我个人习惯把这种叫做“全链处理型”,而前面的审批流程叫做“单节点处理型”。两种都是责任链,只是终止条件不同。
责任链模式的核心流程图
下面我用一张 SVG 图来展示责任链的核心逻辑。这张图我画的是“单节点处理型”,也就是审批流程那种:
责任链模式的优缺点
| 优点 | 缺点 |
|---|---|
| 1. 解耦请求发送者和接收者 | 1. 请求可能不被处理(链尾无人处理) |
| 2. 可以动态调整链的顺序和节点 | 2. 调试时不太直观,链太长影响性能 |
| 3. 符合开闭原则,新增节点不影响现有代码 | 3. 容易形成“过长的链”,维护成本上升 |
| 4. 每个节点职责单一,便于测试 | 4. 如果链的组装逻辑复杂,容易出错 |
实际项目中的经验
我在项目中用过责任链模式的地方还挺多的,除了审批流程,还有:
- 数据校验链:非空校验 → 格式校验 → 业务规则校验 → 持久化
- 日志过滤链:敏感词过滤 → 日志级别过滤 → 输出格式处理
- 支付风控链:黑白名单检查 → 频率限制 → 金额限制 → 风控模型评分
我个人习惯把责任链的节点设计成无状态的,这样每个节点可以复用,也方便单元测试。如果节点需要状态,我会用 ThreadLocal 或者请求上下文来传递,而不是把状态挂在节点上。
小技巧: 如果你用 Spring 框架,可以把每个处理者注册成 Bean,然后用 List 注入,动态组装链。这样新增一个审批节点,只需要加一个类,改一下配置,不用改任何代码。
什么时候不该用责任链?
责任链虽好,但别滥用。我见过有人把简单的 if-else 也硬套成责任链,结果代码反而更复杂了。
我的建议是:如果处理节点不超过3个,且逻辑固定不变,直接用 if-else 更清晰。责任链的优势在于“动态扩展”和“解耦”,如果这两个需求都不存在,就别强行上模式。
另外,如果链太长(超过10个节点),性能会受影响。每个请求都要遍历一遍链,虽然每个节点逻辑简单,但积少成多。这时候可以考虑用“责任链树”或者“策略模式+责任链”来优化。
总结
责任链模式,说白了就是“让每个节点都有机会处理请求,但谁都不强制处理”。它把请求的发送者和接收者彻底解耦,让系统更灵活、更容易扩展。
你在写审批流程、过滤器、拦截器、校验链的时候,都可以想想责任链。但记住:模式是工具,不是目的。用对了,代码清爽;用错了,反而添乱。
好了,今天的内容就到这里。希望你在实际项目中能用上这个模式,少踩一些我当年踩过的坑。