# Chain of Responsibility（职责链模式）

Chain of Responsibility（职责链模式）属于行为型模式。行为型模式不仅描述对象或类的模式，还描述它们之间的通信模式，比如对操作的处理应该如何传递等等。

**意图：使多个对象都有机会处理请求，从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链，并沿着这条链传递该请求，直到有一个对象处理它为止。**

> 几乎所有设计模式，在了解到它之前，笔者就已经在实战中遇到过了，因此设计模式的确是从实践中得出的真知。但另一方面，如果没有实战的理解，单看设计模式是枯燥的，而且难以理解的，因此大家学习设计模式时，要结合实际问题思考。

## 举例子

如果看不懂上面的意图介绍，没有关系，设计模式需要在日常工作里用起来，结合例子可以加深你的理解，下面我准备了三个例子，让你体会什么场景下会用到这种设计模式。

### 中间件机制

设想我们要为一个后端框架实现中间件（知道 Koa 的同学可以理解为 Koa 的洋葱模型），在代码中可以插入任意多个中间件，每个中间件都可以对请求与响应进行处理。

由于每个中间件只响应自己感兴趣的请求，因此只有运行时才知道这个中间件是否会处理请求，那么中间件机制应该如何设计，才能保证其功能和灵活性呢？

### 通用帮助文案

如果一个大型系统中，任何一个模块点击都会弹出帮助文案，但并不是每个模块都有帮助文案的，如果一个模块没有帮助文案，则显示其父级的帮助文案，如果再没有，就继续冒泡到整个应用，展示应用级别的兜底帮助文案。这种系统应该如何设计？

### JS 事件冒泡机制

其实 JS 事件冒泡机制就是个典型的职责链模式，因为任何 DOM 元素都可以监听比如 `onClick`，不仅可以自己响应事件，还可以使用 `event.stopPropagation()` 阻止继续冒泡。

## 意图解释

JS 事件冒泡机制对前端来说太常见了，但我们换个角度，站在点击事件的角度理解，就能重新发现其设计的精妙之处：

点击事件是叠加在每层 dom 上的，由于 dom 对事件的处理和绑定是动态的，浏览器本身不知道哪些地方会处理点击事件，但又要让每层 dom 拥有对点击事件的 “平等处理权”，所以就产生了冒泡机制，与事件阻止冒泡功能。

通用帮助文案和 JS 事件冒泡很类似，只是把点击事件换成了弹出帮助文案罢了，其场景机理是一样的。

说到这，我们可以再重新理解一下职责链模式的意图：

**意图：使多个对象都有机会处理请求，从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链，并沿着这条链传递该请求，直到有一个对象处理它为止。**

请求指的是某个触发机制产生的请求，是一个通用概念。“避免请求的发送者和接收者之间的耦合关系”，指的是如果我们只有一个对象有处理请求的机会，那接收者就与发送者之间耦合了，其他接收者必须通过这个接收者才能继续处理，这种模式不够灵活。

后半句描述的是如何设计，可以实现这个灵活的模式，即将对象连成一条链，沿着链条传递该请求，直到有一个对象处理它为止。还要理解到，任何一个对象都拥有阻断请求继续传递的能力。

在中间件机制的例子中，后端 Web 框架对 Http 请求的处理就是个运用职责链模式的典型案例，因为后端框架要处理的请求是平行关系，任何请求都可能要求被响应，但对请求的处理是通过插件机制拓展的，且对每个请求的处理都是一个链条，存在处理、加工、再处理的逻辑关系。

## 结构图

<img width=600 src="https://img.alicdn.com/imgextra/i4/O1CN01PUN6ed1FnhM2CeZDn_!!6000000000532-2-tps-1126-500.png">

Handler 就是对请求的处理，可以看到这里是一条环路，只要处理完之后就可以交给下一个 Handler 进行处理，可以在中途拦截后中断，也可以穿透整条链路。

`ConcreteHandler` 是具体 Handler 的实现，他们都需要继承 Handler 以具备相同的 `HandleRequest` 方法，这样每一个处理中间件就都拥有了处理能力，使得这些对象连成的链条可以对请求进行传递。

## 代码例子

职责链实现方式非常多，比如 Koa 的洋葱模型实现原理就值得再写一篇文章，感兴趣的同学可以阅读 [co 源码](https://github.com/tj/co)。这里仅介绍最简单场景的实现方案。

职责链的简单实现模式也分为两种，一种是每个对象本身维护到下一个对象的引用，另一种是由 Handler 维护后继者。

下面例子使用 typescript 编写。

```typescript
public class Handler {
  private nextHandler: Handler

  public handle() {
    if(nextHandler) {
      nextHandler.handle()
    }
  }
}
```

每个 Handler 的默认行为就是触发下一个链条的 `handle`，因此什么都不做的话，这个链条是完全打通的，因此我们可以在链条的任何一环进行处理。

处理的方式就是重写 `handle` 函数，我们在重写时，可以维持对 `nextHandler.handle()` 的调用，以使得链条继续向后传递，也可以不调用，从而终止链条向后传递。

## 弊端

职责链模式不保证每个中间件都有机会处理请求，因为中间件顺序的问题，后面中间件可能被前面的中间件阻断，因此当中间件之间存在不信任关系时，职责链模式并不能保证中间件调用的可靠性。

另外就是不要扩大设计模式的使用范围，对一堆对象的连续调用就没必要使用职责链模式，因为职责链适合处理对象数量不确定、是否处理请求由每个对象灵活决定的场景，而确定了对象数量以及是否调用的场景，就没必要使用职责链模式了。

## 总结

职责链模式是插件机制常用的设计模式，在事件机制、请求处理中有广泛的应用。

职责链模式还可以与组合模式组合使用，因为组合模式描述的是一种统一管理的树形结构，每个节点都可以把自己的父节点作为后继节点。实际上 dom 结构就是一种组合模式，事件冒泡就是在其基础上拓展的职责链模式。

> 讨论地址是：[精读《设计模式 - Chain of Responsibility（职责链模式）》· Issue #292 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/292)

**如果你想参与讨论，请 [点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**

> 关注 **前端精读微信公众号**

<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

> 版权声明：自由转载-非商用-非衍生-保持署名（[创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)）
