职责链模式

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

职责链模式的名字非常形象,一系列可能会处理请求的对象被连接成一条链,请求在这些对 象之间依次传递,直到遇到一个可以处理它的对象,我们把这些对象称为链中的节点

场景

​ 假设我们负责一个售卖手机的电商网站,经过分别交纳 500 元定金和 200 元定金的两轮预定后(订单已在此时生成),现在已经到了正式购买的阶段。

​ 在正式购买后,已经支付过 500 元定金的用户会收到 100 元的商城优惠券,200 元定金的用户可以收到 50 元的优惠券,而之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。

  • orderType:订单类型
  • pay:用户是否已经支付定金
  • stock:用于普通用户,普通购买的手机库存数量

代码实现(未使用职责链模式)

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
let order = function (orderType, pay, stock) {
if (orderType === 1) { // 500 元定金购买模式
if (pay === true) { // 已支付定金
console.log("500 元定金预购, 得到 100 优惠券");
} else { // 未支付定金,降级到普通购买模式
if (stock > 0) { // 用于普通购买的手机还有库存
console.log("普通购买, 无优惠券");
} else {
console.log("手机库存不足");
}
}
} else if (orderType === 2) {
if (pay === true) { // 200 元定金购买模式
console.log("200 元定金预购, 得到 50 优惠券");
} else {
if (stock > 0) {
console.log("普通购买, 无优惠券");
} else {
console.log("手机库存不足");
}
}
} else if (orderType === 3) {
if (stock > 0) {
console.log("普通购买, 无优惠券");
} else {
console.log("手机库存不足");
}
}
};
order(1, true, 500); // 输出: 500 元定金预购, 得到 100 优惠券

用职责链模式重构代码

​ 先把 500 元订单、200 元订单以及普通购买分成 3 个函数。

​ 接下来把 orderType、pay、stock 这 3 个字段当作参数传递给 500 元订单函数,如果该函数不符合处理条件,则把这个请求传递给后面的 200 元订单函数,如果 200 元订单函数依然不能处理该请求,则继续传递请求给普通购买函数。

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
let order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log("500 元定金预购,得到 100 优惠券");
} else {
return "nextSuccessor"; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};

let order200 = function (orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log("200 元定金预购,得到 50 优惠券");
} else {
return "nextSuccessor"; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};

let orderNormal = function (orderType, pay, stock) {
if (stock > 0) {
console.log("普通购买,无优惠券");
} else {
console.log("手机库存不足");
}
};

// Chain.prototype.setNextSuccessor 指定在链中的下一个节点
// Chain.prototype.passRequest 传递请求给某个节点
let Chain = function (fn) {
this.fn = fn;
this.successor = null;
};

Chain.prototype.setNextSuccessor = function (successor) {
return this.successor = successor;
};

Chain.prototype.passRequest = function () {
let ret = this.fn.apply(this, arguments);
if (ret === "nextSuccessor") {
return this.successor && this.successor.passRequest.apply(this.successor, arguments);
}
return ret;
};

let chainOrder500 = new Chain(order500);
let chainOrder200 = new Chain(order200);
let chainOrderNormal = new Chain(orderNormal);

chainOrder500.setNextSuccessor(chainOrder200);
chainOrder200.setNextSuccessor(chainOrderNormal);

chainOrder500.passRequest(1, true, 500); // 输出:500 元定金预购,得到 100 优惠券
chainOrder500.passRequest(2, true, 500); // 输出:200 元定金预购,得到 50 优惠券
chainOrder500.passRequest(3, true, 500); // 输出:普通购买,无优惠券
chainOrder500.passRequest(1, false, 0); // 输出:手机库存不足

异步的职责链

​ 在现实开发中,我们经常会遇到一些异步的问题,比如我们要在节点函数中发起一个 ajax 异步请求,异步请求返回的结果才能决定是否继续在职责链中 passRequest。 这时候让节点函数同步返回 “nextSuccessor” 已经没有意义了,所以要给 Chain 类再增加一个原型方法 Chain.prototype.next,表示手动传递请求给职责链中的下一个节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Chain.prototype.next = function () {
return this.successor && this.successor.passRequest.apply(this.successor, arguments);
};

// 异步职责链
let fn1 = new Chain(function () {
console.log(1);
return "nextSuccessor";
});

let fn2 = new Chain(function () {
console.log(2);
let self = this;
setTimeout(function () {
self.next();
}, 1000);
});

let fn3 = new Chain(function () {
console.log(3);
});
fn1.setNextSuccessor(fn2).setNextSuccessor(fn3);
fn1.passRequest();

现在我们得到了一个特殊的链条,请求在链中的节点里传递,但节点有权利决定什么时候把 请求交给下一个节点。可以想象,异步的职责链加上命令模式(把 ajax 请求封装成命令对象),我们可以很方便地创建一个异步 ajax 队列库。

AOP实现职责链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Function.prototype.after = function (fn) {
let self = this;
return function () {
let ret = self.apply(this, arguments);
if (ret === "nextSuccessor") {
return fn.apply(this, arguments);
}
return ret;
};
};

let order = order500.after(order200).after(orderNormal);
order(1, true, 500); // 输出:500 元定金预购,得到 100 优惠券
order(2, true, 500); // 输出:200 元定金预购,得到 50 优惠券
order(1, false, 500); // 输出:普通购买,无优惠券

缺点

但是职责链模式并不能保证某个请求一定会被链中的节点处理,而是径直的从链尾离开,这种情况下,我们可以在链尾增加一个保底的接受者节点来处理这种即将离开链尾的请求

并且职责链模式使得程序中多了一些节点对象,可能在某一次的请求传递过程中,并没有起到实质性的作用,它们的作用仅仅是让请求传递下去,从性能方面考虑,我们要避免过长的职责链

参考

《javascript设计模式与开发实践》

作者

Liang

发布于

2021-02-07

更新于

2021-12-25

许可协议


评论