责任链模式:解耦请求发送者与接收者
责任链模式:概述与动机
责任链模式(Chain of Responsibility)是一种行为设计模式,它允许你将请求沿着一条由多个处理器组成的链进行传递,直到某个处理器处理该请求为止。通过这种方式,请求的发送者与接收者被彻底解耦——发送者无需知道哪个对象最终会处理请求,链的结构和处理逻辑也可以动态调整。
在日常开发中,经常会出现“需要多个对象尝试处理同一请求”的场景,例如:
- 图形界面中的事件冒泡:一个按钮点击可能被按钮自身、其父容器、再到顶层窗口处理。
- Web 框架中的中间件:请求依次经过认证、日志、压缩等中间件。
- 多级审批流程:员工请假依次提交给直属领导、部门经理、人事总监。
如果将这些处理器硬编码集中到一个类中,系统会高度耦合、难以扩展。责任链模式通过将每个处理器独立成对象,并用一条链串联它们,完美解决了这个问题。
责任链模式的结构
一个典型的责任链模式由以下角色构成:
| 角色 | 说明 |
|---|---|
| Handler(处理器接口或抽象类) | 定义处理请求的抽象方法,通常还提供一个方法用于设置下一个处理器。 |
| ConcreteHandler(具体处理器) | 实现处理请求的逻辑,如果能处理则处理,否则将请求转发给下一个处理器。 |
| Client(客户端) | 创建链,并向链的起点提交请求。 |
结构图示(逻辑模型)
Client ──> Handler A ──> Handler B ──> Handler C ──> null
│ │ │
可处理? 可处理? 可处理?
每个处理器持有下一个处理器的引用,形成单向链表。请求沿链传递,直到某个处理器决定消费它(不再继续传递),或到达链尾未被处理。
责任链模式的核心优势:解耦请求发送者与接收者
传统写法中,请求的发送者必须显式调用特定接收者的方法,例如:
if (authService.authenticate(request)) {
logService.log(request);
dataService.process(request);
}
这种代码违背开闭原则:每新增一个处理步骤,都需要修改客户端代码,且客户端必须了解所有潜在接收者。责任链模式将请求发送者从这种混乱中解放出来,客户端只需将请求发给链头,后续的处理逻辑完全由链上的对象自行决定。这带来了真正的发送者-接收者解耦。
责任链模式的标准实现
以下以 Java 语言为例,展示一个简单的责任链模式实现,模拟员工请假审批流程。
1. 定义处理器抽象类
public abstract class LeaveHandler {
protected LeaveHandler next;
// 设置下一个处理器
public LeaveHandler setNext(LeaveHandler next) {
this.next = next;
return next; // 返回next以便链式调用
}
// 处理请求的抽象方法
public abstract void handleRequest(LeaveRequest request);
}
2. 定义请求类
public class LeaveRequest {
private String employeeName;
private int leaveDays;
// 构造器、getter、setter 省略
}
3. 实现具体处理器
// 直属领导:只能批准3天以内假期
public class DirectLeader extends LeaveHandler {
@Override
public void handleRequest(LeaveRequest request) {
if (request.getLeaveDays() <= 3) {
System.out.println("直属领导批准了 " +
request.getEmployeeName() + " 的请假");
} else if (next != null) {
next.handleRequest(request);
}
}
}
// 部门经理:只能批准7天以内假期
public class DeptManager extends LeaveHandler {
@Override
public void handleRequest(LeaveRequest request) {
if (request.getLeaveDays() <= 7) {
System.out.println("部门经理批准了 " +
request.getEmployeeName() + " 的请假");
} else if (next != null) {
next.handleRequest(request);
}
}
}
// 人事总监:可以批准任何天数
public class HRDirector extends LeaveHandler {
@Override
public void handleRequest(LeaveRequest request) {
System.out.println("人事总监批准了 " +
request.getEmployeeName() + " 的请假");
}
}
4. 客户端组装链并使用
public class Client {
public static void main(String[] args) {
LeaveHandler directLeader = new DirectLeader();
LeaveHandler deptManager = new DeptManager();
LeaveHandler hrDirector = new HRDirector();
// 构建链: 直属领导 -> 部门经理 -> 人事总监
directLeader.setNext(deptManager).setNext(hrDirector);
LeaveRequest req1 = new LeaveRequest("张三", 2);
LeaveRequest req2 = new LeaveRequest("李四", 5);
LeaveRequest req3 = new LeaveRequest("王五", 15);
directLeader.handleRequest(req1); // 直属领导批准
directLeader.handleRequest(req2); // 部门经理批准
directLeader.handleRequest(req3); // 人事总监批准
}
}
关键点:客户端只与 directLeader 交互,后续审批流程对客户端完全透明。增加一个新审批层级(如副总经理)时,只需添加一个新的具体处理器,并插入到链中,无需修改客户端代码。
常见变体与实现细节
1. 如何在链中停止传递?
有些需求要求请求被处理后就不再向下传递。在上例中,每个处理器在处理后没有调用 next,自然就停止了。某些设计会要求处理器始终尝试处理,并且无论是否处理都继续传递(类似过滤链)。你需要根据业务决定传递规则。
2. 责任链与纯“洋葱模型”
- 洋葱模型(如 Koa 中间件):请求从外向内穿过所有中间件,再由内向外响应,每个中间件可做前后置处理。
- 单向链:请求被处理后即终止,或即使未处理也依次尝试。责任链模式更偏向单向链。
3. 使用函数式编程简化
在支持函数式编程的语言中,责任链可由一连串函数组合实现:
List<Consumer<LeaveRequest>> handlers = Arrays.asList(
req -> { if (req.getLeaveDays() <= 3) ... },
req -> { if (req.getLeaveDays() <= 7) ... }
);
// 遍历 handlers 直到某个处理并 break,或结合 Optional 实现
尽管灵活,但丢失了“可动态增删链节点”的面向对象优势。
适用场景
当你的代码中有大量顺序依赖的条件处理逻辑时,考虑使用责任链模式:
- 多个对象可能处理同一请求,具体由运行时条件决定。
- 需要向一组对象提交请求,而不想显式指定接收者。
- 需要动态指定处理者集合(例如系统根据配置加载不同的校验插件)。
典型案例:
- Java 的
Filter链、Servlet 的过滤逻辑。 - Spring Security 的安全过滤器链。
- Netty 的
ChannelPipeline。 - 日志框架(如 Log4j)的日志处理器链。
责任链模式的优缺点
优点
- 发送者与接收者解耦,发送者无需知道哪个对象处理请求。
- 增强系统的可扩展性,可以动态添加或修改链,符合开闭原则。
- 可灵活控制处理顺序,通过改变串联顺序即可调整。
- 简化对象,每个处理器只关注自己的逻辑,无需关心其他处理者。
缺点
- 请求可能到达链尾仍未被处理(需提供默认处理或告警机制)。
- 如果链过于长,请求遍历可能影响性能(但通常可忽略)。
- 调试时可能难以跟踪请求经过的完整路径。
- 如果处理者之间存在顺序依赖(必须先 A 后 B),需在构建链时保证,容易出配置错误。
与其他模式的关系
| 模式 | 区别与联系 |
|---|---|
| 装饰器模式 | 装饰器模式强调对功能进行增强,且请求一定会经过每一层装饰;责任链中请求可能中途终止。两者结构相似,但意图不同。 |
| 命令模式 | 命令模式将请求封装为对象并交给调用者执行;责任链中的处理者可以直接处理请求。可结合使用:命令在链中被传递。 |
| 观察者模式 | 观察者是对多个订阅者广播通知,所有观察者都能收到;责任链是按顺序尝试,一个处理者可能独占消费。 |
| 状态模式 | 状态模式通过改变对象内部状态来处理行为变化;责任链用不同对象表示处理行为。 |
总结
责任链模式通过将请求沿一条预定义的链进行传递,实现了请求发送者与最终接收者之间的解耦。它让你可以动态组合、重用和扩展处理逻辑,尤其适合多级别、多步骤的请求处理场景。学习该模式后,你将能更优雅地编写那些充满 if-else 和硬编码调用关系的模块,使系统更灵活、更易于维护。
一句话记忆:把一连串的“尝试处理”变成链上的独立节点,让客户端只知头、不知尾。