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设计模式-职责链模式)