JavaScript设计模式-职责链模式

概念

  职责链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。职责链模式的名字非常形象,一系列可能会处理请求的对象被连接成一条链,请求在这些对象之间依次传递,直到遇到一个可以处理它的对象,把这些对象称为链中的节点。

描述

  我们时常会碰到这种情况,就是有多个对象可以处理一个请求,哪个对象处理该请求事前并不知道,要在运行时刻自动确定,此时,最好的办法就是让请求发送者与具体处理者分离,让客户在不明确指定接受者的情况下,提交一个请求,然后由所有能处理该请求的对象连成一条链,并沿着这条链传递该请求,知道有一个对象处理为止。比如:我住在县城,生了怪病,我不知道什么级别的医院可以诊治,显然最简单的办法就是马上找附近的医院,让此医院来决定是否可以治疗,如果不能,则医院会提供转院建议,由县级转市级、由市级转省级、由省级转 国家级,直到可以治疗为止。

应用

  运用职责链模式可以写出灵活拆分和可重组的代码。
  假设负责一个售卖手机的电商网站,经过分别交纳500元定金和200元定金的两轮预定后(订单已在此时生成),现在已经到了正式购买的阶段。公司针对支付过定金的用户有一定的优惠政策。在正式购买后,已经支付过500元定金的用户会收到100元的商城优惠券,200元定金的用户可以收到50元的优惠券,而之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。
  订单页面是PHP吐出的模板,在页面加载之初,PHP会传递给页面几个字段。
  1、orderType:表示订单类型(定金用户或者普通购买用户),code的值为1的时候是500元定金用户,为2的时候是200元定金用户,为3的时候是普通购买用户
  2、pay:表示用户是否已经支付定金,值为true或者false。虽然用户已经下过500元定金的订单,但如果他一直没有支付定金,现在只能降级进入普通购买模式
  3、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 优惠券

  虽然上面的代码可以得到运行结果,但该order函数不仅巨大到难以阅读,如果接到需求变更的要求,则需要到order函数中更改,上面的代码违反开放封闭原则、单一职责原则,可维护性非常差。

  现在用职责链模式对代码进行重构:
  首先需要改写一下分别表示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.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, true, 500 ); // 输出:普通购买,无优惠券
chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足

通过改进,可以自由灵活地增加、移除和修改链中的节点顺序,假如某天网站运营人员又想出了支持300元定金购买,那就在该链中增加一个节点即可:

varorder300=function(){
  //具体实现略
};

chainOrder300=newChain(order300);
chainOrder500.setNextSuccessor(chainOrder300);
chainOrder300.setNextSuccessor(chainOrder200);

小结

  无论是作用域链、原型链,还是DOM节点中的事件冒泡,都能从中找到职责链模式的影子。

参考文献

《JavaScript设计模式与开发实践》
《JavaScript权威指南》

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • javascript设计模式与开发实践 设计模式 每个设计模式我们需要从三点问题入手: 定义 作用 用法与实现 单...
    穿牛仔裤的蚊子阅读 4,240评论 0 13
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,827评论 2 17
  • 摘自《JavaScript设计模式与开发实践》 职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送...
    小小的白菜阅读 357评论 0 2
  • 孤独啊, 就像是, 昙花一现时的无人欣赏, 盛夏蝉鸣时的无人问津, 苦恼来临时的无人诉说。 孤独的人啊, 他的内心...
    流沽阅读 270评论 1 1
  • 先理解别人,再被别人理解,用耳朵听,用眼睛看,用心去理解,别人说话时要用心去倾听,不要轻易打断他,想要被别人理解尊...
    小龙虾Fineyoga阅读 156评论 0 0