讲解
公司里报销:500 块以内经理批,5000 块以内总监批,再往上 CEO 批。报销单不需要知道该找谁——从直属领导开始递,谁有权谁处理,处理不了就往上递。责任链模式就是把「一串可能处理请求的对象」串成链,请求沿链传递,直到某个对象处理它为止;发送者和接收者因此解耦。
GoF 的定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。链上的每个节点做两件事:判断自己能不能处理;不能就转给下一个。客户端只跟链头打交道。
这个模式有两种流派,都值得认识。纯责任链:一个请求只被一个节点处理(审批流,批了就结束)。不纯的责任链:每个节点都做一点事然后放行(Web 中间件——日志中间件记一笔、鉴权中间件查一下、最后路由中间件出响应,每个都处理又都放行)。Express、Koa 的中间件洋葱模型就是不纯责任链,本教程最后一章的实践篇还会再见到它。
和「一堆 if-else 分派」相比,责任链把分派逻辑摊开成可组合的节点:新增一级审批 = 新增一个节点类 + 插进链里,不改既有节点;链的顺序、长短可以在装配时(甚至运行时)自由调整。代价是请求可能走完整条链也没人处理——要有兜底的「链尾」策略,示例里链尾返回「无人敢批」。
示例
三级审批链:经理、总监、CEO,按金额逐级上递,链尾兜底:
import assert from 'node:assert/strict';
interface Expense {
title: string;
amount: number;
}
abstract class Approver {
private next: Approver | null = null;
// 返回 next 方便链式装配:a.setNext(b).setNext(c)
setNext(next: Approver): Approver {
this.next = next;
return next;
}
handle(expense: Expense): string {
if (this.canApprove(expense.amount)) {
return this.name() + ' 批准了「' + expense.title + '」(' + expense.amount + ' 元)';
}
if (this.next !== null) {
return this.next.handle(expense); // 处理不了,上递
}
return '「' + expense.title + '」金额过大,无人敢批';
}
protected abstract canApprove(amount: number): boolean;
protected abstract name(): string;
}
class Manager extends Approver {
protected canApprove(amount: number): boolean {
return amount <= 500;
}
protected name(): string {
return '经理';
}
}
class Director extends Approver {
protected canApprove(amount: number): boolean {
return amount <= 5000;
}
protected name(): string {
return '总监';
}
}
class Ceo extends Approver {
protected canApprove(): boolean {
return true; // 链尾:什么都批
}
protected name(): string {
return 'CEO';
}
}
const manager = new Manager();
manager.setNext(new Director()).setNext(new Ceo());
assert.equal(manager.handle({ title: '买键盘', amount: 299 }), '经理 批准了「买键盘」(299 元)');
assert.equal(manager.handle({ title: '团建', amount: 3000 }), '总监 批准了「团建」(3000 元)');
assert.equal(manager.handle({ title: '服务器采购', amount: 80000 }), 'CEO 批准了「服务器采购」(80000 元)');
console.log(manager.handle({ title: '团建', amount: 3000 }));
注意请求的发起方(manager.handle(...))从头到尾不知道谁在批——它只认链头。想加一级「副总裁批 5 万以内」,新写一个类插到总监和 CEO 之间即可,经理、总监的代码不动。handle 的兜底分支也很重要:链走到底没人处理时给一个明确答复,好过静默丢失。
常见坑
- 链断没人兜底:请求走完没人处理,返回 undefined 或静默成功,调用方以为处理过了;链尾要么有默认处理器,要么明确抛错。
- 节点里硬编码下一个具体类:节点里 new Director() 就把链的装配权锁死在节点内部了;next 必须由外部注入(setNext)。
- 链过长还不自省:十几级审批往往意味着组织流程本身有问题,模式能解决代码结构,解决不了流程腐坏。
- 节点偷偷改了请求却不说明:中间件流派里节点可以修改请求(加字段、改写参数),但要在接口文档里写清楚谁加了什么,否则下游一脸懵。
- 同步链处理异步任务:节点需要 await(查库判断权限)时整条链得设计成 Promise 版;混用同步链和异步节点是最常见的实现事故。
小结
责任链让请求沿节点链传递,发送者与接收者解耦,增删节点不动老代码;分清「纯」(审批流)与「不纯」(中间件)两种流派,链尾要有兜底。下一章:把请求封装成对象以支持撤销的命令模式。