定义

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

13.1 现实中的职责链

  • 公交车上.人比较多.你从后门上车,经常找不到售票员在哪.因为太挤了.于是你得把公交卡拿出来 交给前面的人 让前面的人给你刷 但是前面的人也不知道 于是前面的人继续往前面传递 直到传递到售票员那里.售票员给你刷卡.
  • 期末考试 你坐在第一排 突然有个问题卡住了.于是你写了一张小纸条往后传 后面的童鞋也不知道 于是再往后传 直到有人会做这个问题为止.

职责链模式的优点就是:请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系

13.2 实际开发中的职责链模式

比如我们现在负责做一家售卖手机的电商网站. 在正式购买之后,已经支付过500元定金的用户会收到100元的商城优惠卷,200元定金的用户会收到50元的优惠卷,而之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠卷,而且在库存有限的情况下不一定保证能够买到.页面加载之初,PHP会传递给页面几个字段

  • orderType: 1为500元用户 2为200元 3为普通用户
  • pay: 用户是否已经支付过定金,值为true或者false
  • stock: 表示当前用于普通用户购买的手机库存数量,已经支付了500元或者200元定金的用户不受此限制12345678910111213141516171819202122232425262728293031var 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){//200元定金购买模式 if(pay=true){//已经支付定金 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元优惠卷

很显然..这段代码可读性基本上为零

13.3 用职责链模式重构代码

先把500元,200元,普通用户分成三个函数.然后把三个参数先传递给500元的 如果不符合要求那么传递给200元的..

//500元订单 

var order500=function(orderType,pay,stock){ 
   if(orderType===1&&pay===true){ 
       console.log("500元定金购买,获得100元优惠卷") 
   }else{ 
       order200(orderType,pay,stock) 
   } 
} 

//200元订单 
var order200=function(orderType,pay,stock){ 
   if(orderType===2&&pay===true){ 
       console.log("200元定金购买,获得50元优惠卷") 
   }else{ 
       orderNormal(orderType,pay,stock) 
   } 
} 

//普通购买订单 
var orderNormal=function(orderType,pay,stock){ 
   if(orderType===3&&pay==true){ 
       console.log("普通用户购买") 
   }else{ 
       console.log("库存不足") 
   } 
} 
   // 测试结果: 
   order500( 1 , true, 500); // 输出:500 元定金预购, 得到100 优惠券 
   order500( 1, false, 500 ); // 输出:普通购买, 无优惠券 
   order500( 2, true, 500 ); // 输出:200 元定金预购, 得到500 优惠券 
   order500( 3, false, 500 ); // 输出:普通购买, 无优惠券 
   order500( 3, false, 0 ); // 输出:手机库存不足 

虽然这段代码看起来很不错了.但是还存在耦合性 传递请求的代码被耦合在了业务函数之中.这显然是违反开放-封闭原则的.假如哪天要增加300元订单或者200订单,意味着就必须更改这些业务函数内部.

13.4 灵活可拆分的职责链模式

本节采用一种更加灵活的方法来改进上面的职责链模式,目标是让链中的各个节点可以灵活拆分和重组.首先需要改写一下分别表示3种购买模式的节点函数.我们约定如果某个节点不能处理请求则返回一个特定字符串”nextSuccessor”来表示该请求需要继续往后传递.

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

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

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

接下来需要把函数包装进职责链节点.我们定义一个构造函数Chain.在new Chain的时候传递的参数即是要被需要包装的函数.同时它还拥有一个实例属性 this.nextSuccessor,表示在链中的下一个节点

var Chain=function(fn){ 
   this.fn=fn; 
   this.successor=null; 
} 
Chain.prototype.setNextSuccessor=function(successor){ 
   this.successor=successor 
} 
Chain.prototype.passRequest=function(){ 
   var ret=this.fn.apply(this,arguments);//运行new时传递进来的函数 
   if(ret=="nextSuccessor"){//如果函数返回的是 nextSuccessor 那么执行下一个函数的passRequest 
       return this.successor&&this.successor.passRequest.apply(this.successor,arguments) 
   } 
   return ret 
} 

然后把三个订单函数包装成职责链的节点

var chainOrder500=new Chain(order500); 
var chainOrder200=new Chain(order200); 
var chainNormal=new Chain(orderNormal); 

设置函数在职责链模式中的顺序

chainOrder500.setNextSuccessor(chainOrder200); 
chainOrder200.setNextSuccessor(chainNormal); 

执行函数

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

13.5 异步的职责链

异步的时候返回nextSuccessor已经是没有意义的了.那么需要构造一个函数Chain.prototype.next 表示手动传递请求给职责链的下一个节点

Chain.prototype.next=function(){ 
   return this.successor&&this.successor.passRequest.apply(this.successor,arguments) 
} 

下面是一个异步职责链的例子

var fn1=new Chain(function(){ 
   console.log(1); 
   return "nextSuccessor"     
}) 
var fn2=new Chain(function(){ 
   console.log(2); 
   var self=this;//指向Chain 
   setTimeout(function(){ 
       self.next() 
   },1000)    
}) 
var fn3=new Chain(function(){ 
   console.log(3); 
}) 

fn1.setNextSuccessor(fn2).setNextSuccessor(fn3) 
fn1.passRequest(); 

..这里的异步链式调用会出错 应该还要改写setNextSuccessor函数

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

13.6 职责链模式的优缺点

职责链模式最大的优点就是解耦了请求发送者和N个接收者之间的复杂关系.由于不知道链中的哪个节点可以处理你发出的请求,所以你需要把请求传递给下一个节点.其次 我们可以手动指定一个节点 比如当天的优惠活动全部搞完了 那么剩下的都是普通用户 我们就可以用普通用户函数来处理

chainNormal(1,false,500);//普通购买 无优惠 

当然 职责链模式的缺点就是 如果查找完整个链条都找不到接收者.那么就会报错. 如果链条太长会带来性能问题.

13.7 用AOP实现职责链

利用JavaScript函数式特性,我们在3.2.3中创建了一个Function.prototype.after函数.这里改写这个函数 使得第一个函数返回”nextSuccessor”时将请求继续传递给下一个函数.

Function.prototype.after=function(fn){ 
   var self=this 
   console.log(212,self) 
   return function(){ 
       var ret=self.apply(this,arguments) 
       console.log(214,this) 
       if(ret==="nextSuccessor"){ 
           return fn.apply(this,arguments) 
       } 
   } 
   return ret 
} 
   var order = order500yuan.after( order200yuan ).after( orderNormal ); 
   order( 1, true, 500 ); // 输出:500 元定金预购,得到100 优惠券 
   order( 2, true, 500 ); // 输出:200 元定金预购,得到50 优惠券 
   order( 1, false, 500 ); // 输出:普通购买,无优惠券 

这里的after中的self是第一个函数 fn是第二函数 比如上例中 after(order200yuan)中的self是order500yuan 其中self.apply(this,arguments)这里的this指向的是这个匿名函数调用者的环境.也就是window. fn指向的是第二个函数也就是order200yuan

13.8 用职责链模式来获取文件上传对象

在第7章有一个用迭代器获取文件上传对象的例子.这里用职责链模式可以更简单

var getActiveUpload=function(){ 
   try{ 
       return new ActiveXObject("xxxx")//IE上传控件 
   }catch(e){ 
       return "nextSuccessor" 
   } 
} 

var getFlashUploadObj=function(){ 
   if(supportFlash()){ 
       return $("<div>xxx</div>").appendTo($("body")) 
   } 
   return "nextSuccessor" 
} 
var getUploadObj=getActiveUpload.after(getFlashUploadObj) 
getUploadObj()//上传方案 

小结

职责链模式还可以与组合模式一起使用 这样威力更大.