职责链模式

概述

职责链模式(Chain of Responsibility)的主要实现逻辑为,将请求的处理对象构造为链式结构,然后在这条链上依序传递请求,直到请求被某个对象处理,或者最终得不到处理。

《设计模式:可复用面向对象软件的基础》一书将职责链模式描述为:

Avoid coupling the sender of a request to its receiver by giving morethan one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

经典实现

职责链模式包含下列组件:

  • Handler: 处理请求对象的抽象接口,包含 handle 抽象方法,用于处理请求或调用下一个处理对象处理请求;setNextHandler 方法(可选),用于设定下一个处理对象;nextHandler 属性为下一个处理对象。
  • ConcerteHandler: 处理请求的具体类,包含 handle, setNextHandler 方法的实现,nextHandler 属性访问下一个处理对象。
  • Client: 用于向链上的具体处理者传递请求。

以下代码使用职责链模式揣测浏览器事件冒泡机制的简要实现。与浏览器不同的是,示例代码只演示父子层级关系中,子节点如何将事件对象转交给父节点,并触发绑定在父节点上的事件处理函数。不过,通过这段简要的代码实现,可以猜想事件对象 stopPropagation 方法的实现。事件对象可在鼠标或键盘点击时构造;在浏览器中,指定下一个处理对象这一过程,也可以在解析 dom 树的时候实现,使父节点自然成为子节点的下一个处理对象。关于事件捕获的实现,疑似通过事件对象的坐标属性和元素的位置属性比对实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
abstract class Handler{
nextHandler;

handle(){}
setNextHandler(nextHandler){
this.nextHandler = nextHandler;
}
};

class Node extends Handler{
clickHandlers = [];

handle(event){
switch(event.type){
case 'onClick':
for ( const handler of this.clickHandlers ){
handler();
};
break;
default:
if ( this.nextHandler ) this.nextHandler.handle(event);
break;
};
}

onClick(handler){
this.clickHandlers.push(handler);
}
};

class ParentNode extends Node{};

class ChildNode extends Node{};

const parentNode = new ParentNode();
const childNode = new ChildNode();
childNode.setNextHandler(parentNode);

const mouseEvent = new MouseEvent();
childNode.handle(mouseEvent);

通过上述代码示例,我们也可以猜想 JavaScript 语言中原型链的实现。

职责链模式具有如下特点:

  1. 处理对象呈链式结构,因此,天然可以用来处理流程。在实现上,可以使用 setNextHandler 方法指定下一个处理对象,也可以使用一条已有的链,如父子结构形成的层级关系,或者队列形式(队列内若存储函数,当函数返回值为 false 时,表示不再将请求转交给下一个处理对象)。通常情况下,职责链模式表现为,根据请求的不同,再唤醒链上的某个处理对象加以操作,效果等同于策略模式。介于其链式处理的特征,职责链模式有一个变种,即请求经由前一个处理对象封装后,再交由下一个处理对象,这和浏览器的事件冒泡机制相仿(这样的链式操作,也可以通过函数队列或者迭代器模式实现)。
  2. 当链未明确指定时,可在链中灵活地添加处理对象、并指定下一个处理对象,跳过不必要的处理逻辑。
  3. 请求对象可以采用最简单的硬编码形式,也可以采用复杂的对象形式、或者使用特定的 Request 类加以构造,甚至使用转换函数从标识符中获取到传入处理对象的数据。这里要指出职责链模式的另一个变种,即可通过处理对象对请求进行特定包装后,再唤醒下一个处理对象。如第一点指出的,对于简单的请求对象,我们可以换用策略模式实现多样的处理机制;甚至对于复杂的请求对象,我们也可以制定规则转换引擎将其转换为简单的标识符形式,再通过策略模式加以处理。但是策略模式只能选中一种算法,且没有呈现出链式结构,因此策略模式不能像职责链模式那样不能用于控制流程,如审批流。
  4. 职责链模式可用于降低请求发送者和接受者的耦合度,处理对象也不需要知道链的结构。
  5. 过长的职责链影响程序的性能,同时也占用内存开销。在某些情况下,传入的请求会得不到处理,这时可以在链的尾端添加一个兜底函数处理请求。

js 中的职责链模式

在 js 中,可借助 AOP 切面实现职责链模式,在实现上呈现出函数式特征。

1
2
3
4
5
6
7
8
9
10
11
12
13
function handler1(){ };
function handler2(){ };

Function.property.after = nextHandler => {
return () => {
let ret = this.apply(this, arguments);
if ( !!ret ) return nextHandler.apply(this, arguments);

return ret;
};
};

handler1.after(handler2);

应用

如前文所说,职责链模式可应用于呈现出父子结构的图形界面上,以实现事件处理或者其他图形效果。

职责链也可用于流程控制,如审批流等。

在已知的应用中,职责链模式见于 servelt 过滤器的实现,redux、koa 中间件的实现,koa-router 路由的实现。

参考

[设计模式:可复用面向对象软件的基础]
[Javascript 设计模式和开发实践 - 曾探]