职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
实际开发中的职责链模式
假设我们负责一个售卖手机的电商网站,经过分别交纳500元定金和200元定金的两轮预定后(订单已在此时生成),现在已经到了正式购买的阶段。
公司针对支付过定金的用户有一定的优惠政策。在正式购买后,已经支付过500元定金的用户会收到100元的商城优惠券,200元定金的用户可以收到50元的优惠券,之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。
字段解释:
- orderType:订单类型,code的值为1的时候是500元定金用户,为2的时候是200元定金用户,为3的时候是普通购买用户。
- pay:表示用户是否已经支付定金,true或false,虽然用户已经下过500元定金的订单,但如果一直没有支付定金,现在只能降级进入普通购买模式。
- stock:表示当前用于普通购买的手机库存数量,已经支付过500元或者200元定金的用户不受此限制。
var 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优惠券
虽然以上代码也可以实现需求,但是难以阅读更难以维护。
用职责链模式重构代码
首先,我们要把500元订单,200元订单以及普通购买分成3个函数。
var order500 = function(orderType,pay,stock){
if(orderType === 1 && pay == true){
console.log('500元定金预购,得到100优惠券');
}else {
order200(orderType,pay,stock);//将请求传递给200元订单
}
}
// 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(stock > 0){
console.log('普通购买,无优惠券');
}else {
console.log('手机库存不足');
}
}
测试结果:
order500(1,true,500);//输出:500元定金预约,得到100优惠券
order500(1,false,200);//输出:普通购买,无优惠券
order500(2,true,500);//输出:200元定金预购,得到50优惠券
order500(3,false,500);//普通购买,无优惠券
order500(3,false,0);//输出:手机库存不足
可以看到,改过的代码结构清晰了很多,把一个大函数拆成了3个函数,去掉了许多嵌套的条件分支语句。
但是上面代码依然会有一个问题,请求在链条传递中的顺序非常僵硬,传递请求的代码被耦合在了业务函数之中,这是违反开放-封闭原则的。
灵活可拆分的职责链节点
为了让链中的各个节点可以灵活拆分和重组,首先需要改写一下分别表示3种购买模式的节点函数,如果某个节点不能处理请求,则返回一个特定的字符串‘nextSuccessor’来表示该请求需要继续往后传递:
var order500 = function(orderType,pay,stock){
if(orderType === 1 && pay == true){
console.log('500元定金预购,得到100优惠券');
}else {
return 'nextSuccessor';//我不知道下一个节点是谁,反正把请求往后面传递
}
}
// 200元订单
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.successor,表示在链中的下一个节点。
var Chain = function(fn){
this.fn = fn;
this.successor = null;
}
//指定在链中的下一个节点
Chain.prototype.setNextSuccessor = function(successor){
return this.successor = successor;
}
//传递请求给某个节点
Chain.prototype.passRequest = function(){
var ret = this.fn.apply(this,arguments);
if(ret === 'nextSuccessor'){
return this.successor && this.successor.passRequest.apply(this.successor,arguments);
}
return ret;
}
把3个订单函数分别包装成职责链的节点:
var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var 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,false,500);//输出:普通购买,无优惠券
chainOrder500.passRequest(1,false,0);//输出:手机库存不足
通过改进,我们可以自由灵活地增加、移除和修改链中的节点顺序。
异步的职责链
在现实开发中,我们经常会遇到一些异步问题,比如我们要在节点函数中发起一个Ajax异步请求,异步请求返回的结果才能决定是否继续在职责链中passRequest。
这时候让节点同步返回“nextSuccessor”已经没有意义了,所以要给Chain类再增加一个原型方法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;
setTimeout(function(){
self.next();
},1000)
})
var fn3 = new Chain(function(){
console.log(3)
});
fn1.setNextSuccessor(fn2).setNextSuccessor(fn3);
fn1.passRequest();
用AOP实现职责链
下面我们改写一下第三章的Function.prototype.after 函数:
Function.prototype.after = function(fn){
var _self = this;
return function(){
var ret = _self.apply(this,arguments);
if(ret === 'nextSuccessor'){
return fn.apply(this,arguments);
}
return ret;
}
}
用职责链模式获取文件上传对象
改写一下第七章文件上传对象的例子,当时创建了一个迭代器来迭代获取合适的文件上传对象,其实用职责链模式可以更简单,我们完全不用创建这个多余的迭代器:
var supportFlash = function(){
return true;
}
var getActiveXObject = function(){
try{
return new ActiveXObject("TXFTNActiveX.FINUpload");//IE上传控件
}catch(e){
return 'nextSuccessor';
}
}
var getFlashUploadObj = function(){
if(supportFlash()){//supportFlash函数未提供
var str = '<object type="application/x-shockwave-flash"></object>'
return $(str).appendTo($('body'))
}
return 'nextSuccessor';
}
var getFormUpladObj = function(){
var str = '<form><input name="file" type="file"/></form>';//表单上传
return $(str).appendTo($('body'));
}
var getUploadObj = getActiveXObject.after(getFlashUploadObj).after(getFormUpladObj);
console.log(getUploadObj())