职责链的定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象能处理它为止,传递链中的这些对象就叫节点。
需求背景: 一个电商网站,用户交500定金且定金已付时,可享受500优惠券且不受货物数量限制;用户交200定金且定金已付时,可享受500优惠券且不受货物数量限制;用户不交定金时受货物数量限制,有货时原价买,无货时则无法买。
原始版本, if else一路判断
代码语言:javascript复制 1var buyOrder = function(orederType, pay, stock){
2 if(orederType == 1){
3 if(pay){
4 console.log('500优惠券');
5 }else {
6 if(stock > 0){
7 console.log('普通购物页面');
8 }else {
9 console.log('已无货');
10 }
11 }
12 }else if(orederType == 2){
13 if(pay){
14 console.log('200优惠券');
15 }else {
16 if(stock > 0){
17 console.log('普通购物页面');
18 }else {
19 console.log('已无货');
20 }
21 }
22 }else if(orederType == 3){
23 if(stock > 0){
24 console.log('普通购物页面');
25 }else {
26 console.log('已无货');
27 }
28 }
29}
30
31buyOrder(1, true, 600)
改进版本
代码语言:javascript复制 1var order500 = function(orderType, pay , stock){
2 if(orderType == '1' && pay == true){
3 console.log('500优惠券');
4 }else {
5 order200(orderType, pay , stock)
6 }
7}
8
9var order200 = function(orderType, pay , stock){
10 if(orderType == '2' && pay == true){
11 console.log('200优惠券');
12 }else {
13 orderNormal(orderType, pay , stock)
14 }
15}
16
17var orderNormal = function(orderType, pay , stock){
18 if(stock > 0){
19 console.log('普通购物页面');
20 }else {
21 console.log('已无货');
22 }
23}
24
25order500(3, true, 0)
优化版本1: 同步的职责链
代码语言:javascript复制 1//3个订单函数 ,它们都是节点函数
2var order500 = function(orderType, pay , stock){
3 if(orderType == '1' && pay == true){
4 console.log('500优惠券');
5 }else {
6 return 'nextSuccessor'; //我不知道下个节点是谁,反正把请求往后传递
7 }
8}
9
10var order200 = function(orderType, pay , stock){
11 if(orderType == '2' && pay == true){
12 console.log('200优惠券');
13 }else {
14 return 'nextSuccessor'; //我不知道下个节点是谁,反正把请求往后传递
15 }
16}
17
18var orderNormal = function(orderType, pay , stock){
19 if(stock > 0){
20 console.log('普通购物页面');
21 }else {
22 console.log('已无货');
23 }
24}
25
26//职责构造函数
27var Chain = function(fn){
28 this.fn = fn;
29 this.successor = null;
30}
31
32Chain.prototype.setNextSuccessor = function(successor){ //设置职责顺序方法
33 this.successor = successor
34}
35
36Chain.prototype.passRequest = function(){ //请求传递
37 var ret = this.fn.apply(this, arguments)
38
39 if(ret === 'nextSuccessor'){
40 return this.successor && this.successor.passRequest.apply(this.successor, arguments)
41 }
42
43 return ret;
44}
45
46//把3个订单函数分别包装成职责链的节点
47var chainOrder500 = new Chain(order500)
48var chainOrder200 = new Chain(order200)
49var chainOrderNormal = new Chain(orderNormal)
50
51//然后指定节点在职责链中的顺序
52chainOrder500.setNextSuccessor(chainOrder200)
53chainOrder200.setNextSuccessor(chainOrderNormal)
54
55//最后把请求传递给第一个节点,开启职责链模式传递
56chainOrder500.passRequest(1, true, 500) //500优惠券
57chainOrder500.passRequest(3, true, 20) //普通购物页面
58chainOrder500.passRequest(3, true, 0) //已无货
59
60//此时如果中间有需求改动,只需如此做:
61var order300 = function(){
62 if(orderType == '3' && pay == true){
63 console.log('300优惠券');
64 }else {
65 return 'nextSuccessor'; //我不知道下个节点是谁,反正把请求往后传递
66 }
67}
68var chainOrder300 = new Chain(order300) //添加新职责节点
69chainOrder500.setNextSuccessor(chainOrder300)
70chainOrder300.setNextSuccessor(chainOrder300) //修改职责链顺序
71chainOrder200.setNextSuccessor(chainOrderNormal)
72
73//这样,就可以完全不必去理会原来的订单函数代码,只需增加一个节点,然后重新设置职责链中的相关节点的顺序就行。
优化版本2:异步的职责链
在实际开发中,经常会遇到 一些异步的问题,比如要在节点函数中发起一个ajax请求,异步请求返回的结果才能决定是否继续在职责链中passRequest
可以给Chain类再增加一个原型方法:
代码语言:javascript复制 1//职责构造函数
2var Chain = function(fn){
3 this.fn = fn;
4 this.successor = null;
5}
6
7Chain.prototype.setNextSuccessor = function(successor){ //设置职责顺序方法
8 this.successor = successor
9}
10
11Chain.prototype.passRequest = function(){ //请求传递
12 var ret = this.fn.apply(this, arguments)
13
14 if(ret === 'nextSuccessor'){ //传递给职责链中的下一个节点
15 return this.successor && this.successor.passRequest.apply(this.successor, arguments)
16 }
17
18 return ret;
19}
20
21//新增,表示手动传递请求给职责链中的下一个节点
22Chain.prototype.next = function(){
23 return this.successor && this.successor.passRequest.apply(this.successor, arguments)
24}
25
26
27//异步职责链例子
28var fn1 = new Chain(function(){
29 console.log(1);
30 return 'nextSuccessor'
31})
32
33var fn2 = new Chain(function(){
34 console.log(2);
35 var self = this;
36 setTimeout(function(){
37 self.next()
38 }, 1000)
39})
40
41var fn3 = new Chain(function(){
42 console.log(3);
43})
44
45
46//指定节点在职责链中的顺序
47fn1.setNextSuccessor(fn2)
48fn2.setNextSuccessor(fn3)
49
50//把请求传递给第一个节点,开始节点传递
51fn1.passRequest()
52
53//输出 1 2 ...(1秒后)... 3
54
55//这是一个异步职责链,请求在职责链节点中传递,但节点有权利决定什么时候 把请求交给下一个节点。这样可以创建一个异步ajax队列库。
tips:
这里补充个知识点:"短路求值" &&
会返回第一个假值(0, null, "", undefined, NaN)
,而 ||
则会返回第一个真值。
var x = a || b || c
等价于:
1var x;
2if(a){
3 x = a;
4} else if(b){
5 x = b;
6} else {
7 x = c;
8}
var x = a && b && c
等价于:
1var x = a;
2if(a){
3 x = b;
4 if(b){
5 x = c;
6 }
7}
所以 &&
有时候会用来代替 if (expression) doSomething()
,转成 &&
方式就是 expression && doSomething()
。
而 ||
比较用来在函数中设置默认值,比如:
1function doSomething(arg1, arg2, arg3) {
2 arg1 = arg1 || 'arg1Value';
3 arg2 = arg2 || 'arg2Value';
4}
不过还需要看具体的使用场景,就比如如果要求 doSomething()
传入的 arg1 为一个数值,则上面的写法就会出现问题(在传入 0 的时候被认为是一个假值而使用默认值)。
现在个人比较常用的方法只判断是否与 undefined
相等,比如
1function doSomething(arg) {
2 arg = arg !== void 0 ? arg : 0;
3}
职责链模式的优势:解耦请求发送者和N个接收者之间的复杂关系,由于不知道链条中的哪个节点可以处理你发出的请求,所以只需把请求传递给第一个节点就行。
如果在实际开发中,当维护一个含有多个条件分支语句的巨大函数时时,可以使用职责链模式。链中的节点对象可以灵活拆分重组,增加删除节点,且无需改动其他节点函数内的代码。