JavaScript 设计模式 -- 职责链

Posted by Nutlee on 2017-05-03

设计模式本身是个听起来高大上的东西,在《JavaScript 设计模式与开发实践》一书的前言部分有个我喜欢的解释

通俗一点说,设计模式是在某种场合下对某个问题的一种解决方案。如果再通俗一点说,设计模式就是给面向对象软件开发者的一些好的设计取个名字

有趣的是 JavaScript 作为一种松散的没边的语言,各种设计模式事实上是浸入骨髓的,或者说 JS 天生就融入了设计模式,这就带来了其他语言难以比拟的灵活性。

本文主要从实践出发,介绍一下职责链模式。

什么是职责链

职责链的定义是,使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到一个对象处理它为止。

简单的说就是构造一个类,类的内部存在某种关联,可以将一系列的校验对象连接起来并且从链的起点开始启动。

构造 Chain 类

这个是我根据书上的例子,结合同步和异步函数的特点改写的 Chain 类,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Chain {
// 根据处理函数初始化类
constructor(fn) {
this.fn = fn
this.successor = null
}
// 关联下一级
setNext(successor) {
return (this.successor = successor)
}
// 启动校验
passRequest() {
return this.fn.apply(this, arguments)
}
// 向下一级传递
next() {
return this.successor && this.successor.passRequest.apply(this.successor, arguments)
}
}

注意:一般书上使用处理函数返回一个类似 'nextSuccessor' 的标志来向下一级传递,实际业务中这样的可读性和方便性不高,故改为手动调 next() 来处理,兼顾异步。

示例

假设一个问题,一个厂有若干员工如下

1
2
3
4
5
6
7
8
let person = [
{name: 'a', years: 2, sex: 'man'},
{name: 'b', years: 1, sex: 'woman'},
{name: 'c', years: 2, sex: 'woman'},
{name: 'd', years: 5, sex: 'man'},
{name: 'e', years: 10, sex: 'woman'},
{name: 'f', years: 7, sex: 'man'}
]

现在要根据年龄和性别发奖金,规则如下

1
2
3
大于 2年工龄,男,1000
大于 2年工龄,女,1200
大于 1年工龄 500

如果使用传统的流程语句会造成多层的条件语句嵌套,虽然本例多个 if 语句嵌套并不复杂,但考虑到工作中复杂业务,多个条件语句的维护性非常差,相反使用职责链模式可以快速定位改动检查逻辑,可维护性无可比拟。

职责链实例代码

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
const countMoney = (() => {
// 检查 2年 男
let checkYears = function({name, years, sex}) {
if (years >= 2 && sex === 'man') {
console.log(`${name},性别${sex},工龄${years},奖金1000元`)
return
} else {
return this.next({name, years, sex})
}
}
// 检查 2年 女
let checkSex = function({name, years, sex}) {
if (years >= 2 && sex === 'woman') {
console.log(`${name},性别${sex},工龄${years},奖金1200元`)
return
} else {
return this.next({name, years, sex})
}
}
// 检查 1年
let checkDefault = function({name, years, sex}) {
if (years >= 1) {
console.log(`${name},性别${sex},工龄${years},奖金500元`)
} else {
console.log(`${name},性别${sex},工龄${years},无奖金`)
}
}
let fn1 = new Chain(checkYears)
let fn2 = new Chain(checkSex)
let fn3 = new Chain(checkDefault)
fn1.setNext(fn2).setNext(fn3)
return fn1
})()
// 任选一个员工开始检查奖励策略
countMoney.passRequest(person[0])