zhangdizhangdi

职责链模式 ⭐

定义

责任链模式(Chain of Responsibility Pattern / 职责链模式),使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止(或者全部处理完)。

公司请假审批:你请假1天,直属组长直接批了;请假3天,组长批不了,系统自动流转给部门总监;请假10天,总监也批不了,流转给大老板。你(发送者)不需要去查到底该找谁,你只需要把请假条递给系统,它会自动顺着“责任链”往上走,直到有人能处理。

痛点:当一个请求需要经过多重条件判断和多重角色处理时,如果把所有逻辑写在一个大函数里,不仅极度臃肿,而且一旦修改其中一环,很容易波及其他环。
解决:将每一个处理节点封装成独立的对象,并在内部维持对下一个节点的引用。节点只需决定“我能处理吗?能处理就结束,不能处理就踢给下一个人”。

与设计原则的关系

  1. 单一职责原则 (Single Responsibility Principle)
    • 责任链上的每个节点(处理者)只关心自己的处理逻辑和职责范围。比如“组长节点”只管 ≤ 3 天的判断,非常纯粹。
  2. 开闭原则 (Open-Closed Principle)
    • 如果你想在“总监”和“大老板”之间插一个“副总裁”节点,只需要新建一个副总裁类,然后修改链条的连接顺序即可。不需要改动原来组长、总监内部的审批代码。
  3. 极度解耦(控制反转的思想)
    • 请求发送方和接收方完全解耦。发送方甚至不知道到底是链条上的哪一个节点最终处理了请求。

实现

1. 传统 OOP 经典实现(链表式结构)

经典的责任链,各个节点通过保存 next 指针(引用)连接成一条链。

javascript
// 1. 处理者基类 (定义结构与传递逻辑)
class Approver {
  constructor(name) {
    this.name = name;
    this.nextApprover = null; // 指向下一个节点
  }
  setNext(approver) {
    this.nextApprover = approver;
    return approver; // 方便链式调用
  }
  // 留给子类实现
  handleRequest(days) {}
}

// 2. 具体的处理者(组长、总监、CEO)
class Manager extends Approver {
  handleRequest(days) {
    if (days <= 3) {
      console.log(`${this.name} (组长) 批准了 ${days} 天的请假。`);
    } else if (this.nextApprover) {
      console.log(`${this.name} 权限不足,移交上级...`);
      this.nextApprover.handleRequest(days); // 往后传!
    }
  }
}

class Director extends Approver {
  handleRequest(days) {
    if (days <= 7) {
      console.log(`${this.name} (总监) 批准了 ${days} 天的请假。`);
    } else if (this.nextApprover) {
      console.log(`${this.name} 权限不足,移交上级...`);
      this.nextApprover.handleRequest(days);
    }
  }
}

class CEO extends Approver {
  handleRequest(days) {
    console.log(`${this.name} (CEO) 最终批准了 ${days} 天的请假。`);
  }
}

// 3. 组装责任链与使用
const manager = new Manager('张三');
const director = new Director('李四');
const ceo = new CEO('王老板');

// 关键步骤:把他们串联起来
manager.setNext(director).setNext(ceo);

// 客户端只管把请求丢给责任链的头部
console.log('--- 申请 2 天请假 ---');
manager.handleRequest(2);
// 张三 (组长) 批准了 2 天的请假。

console.log('\n--- 申请 5 天请假 ---');
manager.handleRequest(5);
// 张三 权限不足,移交上级...
// 李四 (总监) 批准了 5 天的请假。

console.log('\n--- 申请 15 天请假 ---');
manager.handleRequest(15);
// 张三 权限不足,移交上级...
// 李四 权限不足,移交上级...
// 王老板 (CEO) 最终批准了 15 天的请假。

2. 现代 JS 与后端的灵魂:数组变体与 next()

在现代 JavaScript(尤其是 Node.js 生态)中,我们很少用链表去实现责任链,而是用数组把函数存起来,然后提供一个 next() 方法去驱动它遍历。这通常被称为 “中间件模式 (Middleware)”

javascript
// 极简版的中间件(责任链)调度器
class Chain {
  constructor() {
    this.middlewares =[]; // 用数组代替链表
    this.index = 0;
  }
  use(fn) {
    this.middlewares.push(fn);
  }
  run(context) {
    const next = () => {
      const middleware = this.middlewares[this.index++];
      if (middleware) {
        // 将 next 传给中间件,由中间件决定是否继续往下传
        middleware(context, next); 
      }
    };
    next();
  }
}

const app = new Chain();
// 第一个节点验证登录
app.use((ctx, next) => {
  if (ctx.isLogin) {
    console.log('登录校验通过');
    next(); // 走到这才会去下一个节点
  } else {
    console.log('未登录,请求被拦截'); // 链条在此断开
  }
});

// 第二个节点处理业务
app.use((ctx, next) => {
  console.log('正在获取核心数据...', ctx.data);
  next();
});

app.run({ isLogin: true, data: '订单列表' });

前端应用

如果你觉得策略模式在前端最常用,那么责任链模式可以说是现代 Web 框架的基石

1. DOM 事件冒泡 (Event Bubbling)

这是浏览器天然的责任链模式。
当你点击了一个 <button>,浏览器会将点击事件沿着 DOM 树从内向外传递:buttondivbodydocument
这正是一条责任链!如果在某个节点调用了 event.stopPropagation(),就相当于截断了责任链,不再向上抛出。

2. Axios 的拦截器 (Interceptors)

我们在前端发请求时必用的 Axios,其请求拦截器(Request)和响应拦截器(Response)底层使用的完全是责任链模式。

javascript
// 添加请求拦截器(相当于责任链的节点)
axios.interceptors.request.use(function (config) {
    // 节点1:给 config 加 token
    config.headers.token = 'xxx';
    return config; // 传给下一个节点
});

Axios 内部将所有的拦截器放进一个大数组里,然后用一个类似 Promise 链的结构把它们依次串联执行。

3. Webpack 的 Loaders

Webpack 处理源文件时,比如要把 Less 转成最终的 CSS 注入到页面中,通常配置如下:

javascript
use:['style-loader', 'css-loader', 'less-loader']

这是一个加工型责任链:源码先进 less-loader 变成 CSS,再进 css-loader 处理 url() 等依赖,最后进 style-loader 把样式打进 DOM 中。一环扣一环,每个 loader 职责单一。

4. Express / Koa 的中间件洋葱模型

如果你接触过 Node.js 服务端开发,无论是 Express 的中间件,还是 Koa 著名的**“洋葱模型”**,本质上就是责任链模式的巅峰应用。
每一个 app.use((req, res, next) => { ... }) 都是链上的一个节点,只有调用 next() 才会将执行权移交给下一个处理者。如果某一层缓存命中了,它可以直接不调 next() 从而提前结束响应。

总结

当你遇到**“根据条件不同,去挑一个算法执行”时,请使用策略模式**(平行并列)。

当你遇到**“一个请求需要经过层层过滤、步步加工、或者寻找能最终处理它的人”时,请使用责任链模式**(串联成链)。
它把复杂的流程化逻辑切分成一个个小而美的函数/类,是编写具有高扩展性系统(如插件系统、拦截系统)的无上利器。