Libon

责任链模式(ChainOfResponsibility Pattern) - 设计模式

4 分钟 #design-patterns
通过使用责任链模式来优化代码中的大量 if/else 判断。

ToC

假设项目中有这样一个需求:

  1. orderType 参数的值有三种情况
    • === 1 时表示 500 定金
    • === 2 时表示 200 定金
    • === 3 是表示 无优惠券,原价购买
  2. pay 表示是否支付定金
  3. stack 表示还有多少库存

那么根据需求大概可以写出这样的代码:

1
function order(orderType, pay, stack) {
2
if (orderType === 1) { // 500 定金
3
if (pay === true) {
4
console.log('支付 500 定金成功')
5
} else {
6
if (stack > 0) {
7
console.log('普通订单, 无优惠券')
8
} else {
9
console.log('库存不足')
24 collapsed lines
10
}
11
}
12
} else if (orderType === 2) { // 200 定金
13
if (pay === true) {
14
console.log('支付 200 定金成功')
15
} else {
16
if (stack > 0) {
17
console.log('普通订单, 无优惠券')
18
} else {
19
console.log('库存不足')
20
}
21
}
22
} else if (orderType === 3) {
23
if (stack > 0) {
24
console.log('普通订单, 无优惠券')
25
} else {
26
console.log('库存不足')
27
}
28
}
29
}
30
31
order(1, true, 10)
32
order(2, false, 10)
33
order(3, true, 0)

这么做确实可以完成需求, 但是代码并不好看, 同时层级嵌套也比较深, 用了大量的 if/else. 根据这种场景就引申除了这次的主题: 责任链模式.

责任链模式 : 每个函数只处理一种场景的情况(类似单一职责), 如果不符合条件则转交个下一个方法处理, 这种串联的关系就是责任链模式.

根据责任链原则, 可以得出第一版代码:

1
function order500(orderType, pay, stack) {
2
if (orderType === 1 && pay === true) {
3
console.log('支付 500 定金成功')
4
} else {
5
// orderNormal(orderType, pay, stack)
6
return 'next'
7
}
8
}
9
49 collapsed lines
10
function order200(orderType, pay, stack) {
11
if (orderType === 2 && pay === true) {
12
console.log('支付 200 定金成功')
13
} else {
14
// orderNormal(orderType, pay, stack)
15
return 'next'
16
}
17
}
18
19
function orderNormal(orderType, pay, stack) {
20
if (stack > 0) {
21
console.log('普通订单, 无优惠券')
22
} else {
23
console.log('库存不足')
24
}
25
}
26
27
function Chain(fn) {
28
this.fn = fn
29
this.next = null
30
}
31
32
Chain.prototype.set = function (fn) {
33
this.next = fn
34
}
35
36
Chain.prototype.run = function () {
37
const result = this.fn.apply(this, arguments)
38
39
// 如果得到的返回值是 next, 则转交下一个方法处理
40
if (result === 'next') {
41
// 如果没有下一个方法, 则会异常
42
return this.next && this.next.run.apply(this.next, arguments)
43
}
44
}
45
46
// 包装函数, 保证每次调用都会返回一个新的 Chain 对象
47
var chainOrder500 = new Chain(order500)
48
var chainOrder200 = new Chain(order200)
49
var chainOrderNormal = new Chain(orderNormal)
50
51
// 设定链条关系
52
chainOrder500.set(chainOrder200)
53
chainOrder200.set(chainOrderNormal)
54
55
// 运行这个链条
56
chainOrder500.run(1, true, 10)
57
chainOrder500.run(2, false, 10)
58
chainOrder500.run(3, true, 0)

但这种方式还是比较麻烦, 需要对每一个方法进行包装, 可以在 Function 的原型上添加额外的方法, 使用链式调用的方式来优化这个问题.

1
Function.prototype.after = function (fn) {
2
const self = this
3
return function () {
4
const result = self.apply(this, arguments)
5
6
return result === 'next'
7
? fn.apply(this, arguments)
8
: result
9
}
7 collapsed lines
10
}
11
12
var order = order500.after(order200).after(orderNormal)
13
14
order(1, true, 10)
15
order(2, false, 10)
16
order(3, true, 0)

这么做确实解决了问题, 但是日常开发中不应该去给内置构造函数添加方法, 因为这会导致后续接手代码的人很困惑, “为什么这个函数可以直接调用方法, 却没看到定义?”, 所以我们可以自己定义一个构造函数, 然后在原型上添加方法, 最后用实例化后的对象来操作:

1
function Chain2() {
2
this.chain = []
3
}
4
5
Chain2.prototype.add = function (fn) {
6
this.chain.push(fn)
7
return this
8
}
9
22 collapsed lines
10
Chain2.prototype.run = function () {
11
const self = this
12
const args = arguments
13
14
for (let i = 0; i < this.chain.length; i++) {
15
const result = this.chain[i].apply(this, args)
16
17
if (result === 'next') {
18
continue
19
} else {
20
return result
21
}
22
}
23
}
24
25
const chain = new Chain2()
26
27
chain.add(order500).add(order200).add(orderNormal)
28
29
chain.run(1, true, 10)
30
chain.run(2, false, 10)
31
chain.run(3, true, 0)

以上.


CD ..