责(设计模式)任链模式:代码世界里的击鼓传花
责任链模式:代码世界里的击鼓传花
假如你在做一个 OA 系统的请假审批功能。
需求文档摆在桌上,写得清清楚楚:请假不超过 3 天,主任审;3 到 10 天,经理审;10 到 30 天,总经理审;超过 30 天,一律拒绝。
刚开始写代码的时候,我们会毫不犹豫地打开if-else:
public void approve(int days) {
if (days < 3) {
director.approve();
} else if (days < 10) {
manager.approve();
} else if (days < 30) {
generalManager.approve();
} else {
System.out.println("拒绝!");
}
}
写完一看,逻辑没毛病。
但产品经理走过来说:「以后可能要加副总裁审批,超过 30 天的让他来处理。」
想必我们的表情会当场垮了。
问题出在哪里
表面上代码能跑,本质上耦合死了。
审批逻辑全集中在一个方法里,每加一个层级,就要改这段代码。每次改动,都可能引入新的 bug。更糟的是:负责审批的人(Director、Manager、GeneralManager)根本不知道彼此的存在,却被一个外部方法强行调度。
这违反了一个基本原则:每个处理者应该只知道"我能不能处理",以及"处理不了就交给谁"。
这,就是责任链模式要解决的问题。
责任链模式是什么
GoF 给出的定义很简洁:
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
翻译成人话:把处理者串成一条链,请求从头开始一个个往后传,谁能处理谁接手,处理不了就继续传。
类图长这样:
Client ──→ Handler(抽象)
│ successor(指向下一个)
├── ConcreteHandler1
└── ConcreteHandler2
每个 ConcreteHandler 持有一个 successor 指针,指向链上的下一个处理者。这个结构有点像链表——每个节点只认识自己的下一个,不需要了解整条链。
用责任链重写假条审批
先定义抽象处理者,它只做两件事:持有后继者、声明处理方法。
public abstract class Approver {
protected String name;
protected Approver successor; // 后继者,指向链上的下一个
public Approver(String name) {
this.name = name;
}
public void setSuccessor(Approver successor) {
this.successor = successor;
}
// 子类各自实现:我能处理吗?不能就往后传
public abstract void approve(int days);
}
三个具体处理者,每个只关心自己的审批权限:
// 主任:只处理 3 天以内的请假
public class Director extends Approver {
public Director() { super("主任"); }
@Override
public void approve(int days) {
if (days < 3) {
System.out.println(name + " 审批通过,请假 " + days + " 天");
} else if (successor != null) {
successor.approve(days); // 超出权限,往上传
}
}
}
// 经理:处理 3~10 天
public class Manager extends Approver {
public Manager() { super("经理"); }
@Override
public void approve(int days) {
if (days >= 3 && days < 10) {
System.out.println(name + " 审批通过,请假 " + days + " 天");
} else if (successor != null) {
successor.approve(days);
}
}
}
// 总经理:处理 10~30 天,超出则直接拒绝
public class GeneralManager extends Approver {
public GeneralManager() { super("总经理"); }
@Override
public void approve(int days) {
if (days >= 10 && days < 30) {
System.out.println(name + " 审批通过,请假 " + days + " 天");
} else {
System.out.println("请假 " + days + " 天,超出审批权限,申请拒绝!");
}
}
}
组装责任链:
public class Client {
public static void main(String[] args) {
Approver director = new Director();
Approver manager = new Manager();
Approver general = new GeneralManager();
// 组链:主任 → 经理 → 总经理
director.setSuccessor(manager);
manager.setSuccessor(general);
// 所有请求都从链头进入,不需要关心谁来处理
director.approve(2); // 主任审批通过,请假 2 天
director.approve(7); // 经理审批通过,请假 7 天
director.approve(20); // 总经理审批通过,请假 20 天
director.approve(35); // 请假 35 天,超出审批权限,申请拒绝!
}
}
现在,产品经理来说要加副总裁了——完全不慌。
// 新增一个处理者,改一下链的组装方式,其他代码一行不动
Approver vp = new VicePresident();
general.setSuccessor(vp); // 总经理 → 副总裁
开闭原则:对扩展开放,对修改关闭。这才是正确的姿势。
责任链不是万能的
有个问题是:责任链"不可以"做到的事是什么?
答案是:高效率地处理请求。
责任链本质上是顺序遍历。最坏情况下,一个请求要走完整条链才能找到处理者,或者根本无人处理。它换来的是灵活性和解耦,代价是效率。
如果你有一个需求,要求精准匹配、直接命中处理者,那应该用策略模式或者简单的 Map 路由,而不是责任链。
用模式,要知道它的边界。
工作中它长什么样
责任链在框架层面其实无处不在,只是换了个名字:
Spring Security 的过滤器链
请求进来
→ UsernamePasswordAuthenticationFilter
→ BasicAuthenticationFilter
→ ExceptionTranslationFilter
→ FilterSecurityInterceptor
→ 到达 Controller
每个 Filter 都可以选择:处理后继续传、处理后中断、直接拦截返回。这是工业级的责任链实现。
Servlet Filter
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
// 前置处理(可以在这里做权限校验、日志记录)
System.out.println("请求进来了");
chain.doFilter(req, res); // 传给链上的下一个
// 后置处理
System.out.println("响应出去了");
}
}
FilterChain 就是那根链,chain.doFilter() 就是在说:我处理完了,传给下一个吧。
Netty 的 ChannelPipeline 也是同样的设计,所有入站出站事件都沿 pipeline 流动。
识别责任链的信号
读需求文档的时候,如果你看到这些描述,几乎可以断定是责任链的场景:
- "按级别处理":权限不够就往上报
- "依次判断":一个个条件往下过
- "不归我管就转交":处理者之间有传递关系
- "审批流、过滤器、拦截器":这三个词出现,基本就是它了
最后说一句
古代官府有个制度叫「层层上报」——小事县令决,大事知府定,军国大事上达天听。每一级官员只需知道:这件事我能不能拍板,不能就往上递。
千年前的行政智慧,被我们封装进了一个设计模式。
代码的世界里,好的设计从来不是凭空发明的。它们藏在历史的褶皱里,等着被重新发现。
2026.03.02 12:06
沪 · 赵巷