JavaScript设计模式笔记-职责链模式
定义
职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止.
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()//上传方案
小结
职责链模式还可以与组合模式一起使用 这样威力更大.