• 《javascript设计模式与开发实践》阅读笔记(13)—— 职责链模式


     

    职责链模式

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

     

    书里的订单的例子

    假设我们负责一个售卖手机的电商网站,经过分别交纳500元定金和200元定金的两轮预定(订单已在此时生成),现在已经到了正式购买的阶段。公司针对支付过定金的用户有一定的优惠政策。在正式购买后,已经支付过500元定金的用户会收到100元的商城优惠券,200元定金的用户可以收到50元的优惠券,而之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。

    orderType:表示订单类型(定金用户或者普通购买用户),值为1的时候是500元定金用户,为2的时候是200元定金用户,为3的时候是普通购买用户。
    pay:表示用户是否已经支付定金,值为true表示已付钱,如果没有支付定金,只能降级进入普通购买模式。
    num:表示当前用于普通购买的手机库存数量,已经支付过500元或者200元定金的用户不受此限制。

     1     var order=function( orderType, pay, num ){
     2         if ( orderType === 1 ){        // 500元定金购买模式
     3             if ( pay === true ){              // 已支付定金
     4                 console.log( '500元定金预购, 得到100优惠券' );
     5             }else{                            // 未支付定金,降级到普通购买模式
     6                 if ( num > 0 ){                      // 用于普通购买的手机还有库存
     7                     console.log( '普通购买, 无优惠券' );
     8                 }else{
     9                     console.log( '手机库存不足' );
    10                 }
    11             }
    12         }else if ( orderType === 2 ){  // 200元定金购买模式
    13             if ( pay === true ){
    14                 console.log( '200元定金预购, 得到50优惠券' );
    15             }else{
    16                 if ( num > 0 ){
    17                     console.log( '普通购买, 无优惠券' );
    18                 }else{
    19                     console.log( '手机库存不足' );
    20                 }
    21             }
    22         }else if ( orderType === 3 ){   //普通购买
    23             if ( num > 0 ){
    24                 console.log( '普通购买, 无优惠券' );
    25             }else{
    26                 console.log( '手机库存不足' );
    27             }
    28         }
    29     }
    30 
    31     order( 1 , true, 500); // 500元定金预购, 得到100优惠券

    上面的代码其实不用看完就已经能感受到十分的繁琐,把所有的逻辑都列出来使得函数变得十分臃肿。这种类似的代码在策略模式中其实出现过,最后使用策略模式很好的消化了内部的条件分支,这里的话我们用职责链模式也能解决。

     

    使用职责链的思想重写函数

    思路:彼此委托,我可以执行我就执行,我执行不了我就委托给别人执行,所以我们把500订单的执行,200订单的执行和普通购买的执行分别写成一个函数。

     1     var order500=function( orderType, pay, num ){
     2         if ( orderType === 1 && pay === true ){    //如果是500订单且已经付款
     3             console.log( '500元定金预购, 得到100优惠券' );
     4         }else{
     5             order200( orderType, pay, num );      //将请求传递给200 元订单
     6         }
     7     };
     8 
     9     var order200=function( orderType, pay, num ){
    10         if ( orderType === 2 && pay === true ){    //如果是200订单且已经付款
    11             console.log( '200元定金预购, 得到50优惠券' );
    12         }else{
    13             orderNormal( orderType, pay, num );  //将请求传递给普通订单
    14         }
    15     };
    16 
    17     var orderNormal=function( orderType, pay, num ){
    18         if ( num > 0 ){             //普通购买,如果数量足够
    19             console.log( '普通购买, 无优惠券' );
    20         }else{
    21             console.log( '手机库存不足' );
    22         }
    23     }
    24 
    25     order500( 1 , true, 500 );   //500元定金预购, 得到100优惠券
    26     order500( 1, false, 500 );   //普通购买, 无优惠券
    27     order500( 2 , true, 500 );   //200元定金预购, 得到50优惠券

    这个函数比之前的已经好了很多,但是还是有些缺陷,假设说我们增添了300元的订单,那么我们就要改写上面的函数。

    可以说,只要有点改动,无论是增加还是删除哪一环,我们必须拆开这个链条才行,而且必须修改函数内部。

    那这个问题的根源在哪里,传递请求是职责链模式的根本,也是这个函数和上一个函数最大的区别所在,所以传递请求这一点是没有问题的,我们需要他们一个委托一个,那么问题的所在就是委托函数之间耦合的太死,以至于改动必须深入内部才行。所以,我们要想办法降低委托函数彼此间的耦合。

     

    进一步修改函数

    思路:想要解耦合,那么就是用变量代替具体的函数,然后用一个对象安排每个函数之后的委托函数。但是每个函数后面的委托函数是不一样的,所以不能使用相同的变量,但是如果使用不同的变量,其实质和现在并没有多少区别。

    从委托这个字眼,我们想到对象之间可以很方便的调用方法,对象接收这个函数,并产生相应的属性,包括当前的函数,和之后需要执行的委托函数。仔细一想,我们的做法其实相当于扩展原来的函数,在不改动原来函数的基础上,我们想要实现委托,那就只有把函数变成参数来生成一个更为全能的对象,然后让这几个功能更强的对象之间彼此交互。因为这些对象彼此类似,功能也相同,所以我们需要一个模板来生成这些对象。同时为了让对象可以意识到该委托别的对象了,函数的返回值应该做一个统一规定。

     1     /**首先是具体的功能函数**/
     2     var order500=function( orderType, pay, num ){
     3         if ( orderType === 1 && pay === true ){    //如果是500订单且已经付款
     4             console.log( '500元定金预购, 得到100优惠券' );
     5         }else{
     6             return "next"    //统一规定的执行结果
     7         }
     8     };
     9 
    10     var order200=function( orderType, pay, num ){
    11         if ( orderType === 2 && pay === true ){    //如果是200订单且已经付款
    12             console.log( '200元定金预购, 得到50优惠券' );
    13         }else{
    14             return "next"
    15         }
    16     };
    17 
    18     var orderNormal=function( orderType, pay, num ){
    19         if ( num > 0 ){             //普通购买,如果数量足够
    20             console.log( '普通购买, 无优惠券' );
    21         }else{
    22             console.log( '手机库存不足' );
    23         }
    24     }
    25 
    26     /**闭包创建一个类,实例职责对象,参数就是上面的功能函数**/
    27     var order=(function(){
    28         var constructor=function( fn ){  //构造器,存储当前的函数和后面委托的对象
    29             this.fn=fn;
    30             this.next=null;
    31         }
    32         constructor.prototype.setnext=function( nextobj ){ //设定委托的对象
    33             return this.next=nextobj;
    34         }
    35         constructor.prototype.do=function(){  //执行函数
    36             var result=this.fn.apply(this,arguments);  //获得执行结果
    37 
    38             if( result==="next" ){  //当执行结果为规定值时
    39                 return this.next.do.apply(this.next,arguments);  //委托对象执行函数
    40             }
    41             return result;
    42         }
    43 
    44         return constructor;
    45     })();
    46 
    47     /**实例过程**/
    48     var order_500=new order( order500 );
    49     var order_200=new order( order200 );
    50     var order_normal=new order( orderNormal );
    51 
    52     /**职责对象间设定委托的链条关系**/
    53     order_500.setnext( order_200 );
    54     order_200.setnext( order_normal );
    55 
    56     /**初始调用的接口**/
    57     /**这里其实可以再包装一个固定的接口,这样不管链条究竟从哪里开始,我们也不需要改变初始的调用函数**/
    58     order_500.do( 2, true, 500 );  //200元定金预购, 得到50优惠券

    这时候如果加了300元的订单也很方便

    1     var order_300=new ( order300 );
    2     order_500.setnext( order_300 );
    3     order_300.setnext( order_200 );

    小结:我们把手动的修改,变成了通过对象控制,这样只需要几个句子就能很方便的修改业务逻辑,不用去改动任何源码。

    用es6的类来完成

     1     class order{
     2         constructor( fn ){
     3             this.fn=fn;
     4             this.next=null;
     5         }
     6         setnext( nextobj ){
     7             return this.next=nextobj;
     8         }
     9         do(){
    10             var result=this.fn.apply(this,arguments);  //获得执行结果
    11 
    12             if( result==="next" ){  //当执行结果为规定值时
    13                 return this.next.do.apply(this.next,arguments);  //委托对象执行函数
    14             }
    15             return result;
    16         }
    17     }

    异步职责链

    上面的代码我们是约定好了一个返回值,并把这个返回值写死在了原型方法上,这样我们可以直接得到最终答案,但实际上很多时候,可能我们函数的执行需要等待一个ajax的响应,这种时候时间上是不同步的,而且返回的执行结果我们也不能保证一致,所以我们可以添加一个方法,用来手动触发下一个委托函数。

     1     constructor.prototype.nextfun= function(){
     2         return this.next && this.next.do.apply( this.next, arguments );  //如果next存在  就执行它的方法
     3     };
     4 
     5     var fn1 = new order(function(){
     6         console.log( 1 );
     7         return 'next';
     8     });
     9     var fn2 = new order(function(){
    10         console.log( 2 );
    11         var self = this;
    12         setTimeout(function(){
    13             self.nextfun();
    14         }, 1000 );
    15     });
    16 
    17     var fn3 = new order(function(){
    18         console.log( 3 );
    19     });
    20     fn1.setnext( fn2 ).setnext( fn3 );  //链式调用,因为setnext方法返回了对象
    21     fn1.do();  //先出现1,2  隔了一秒之后出现3

    职责链模式的优缺点

    优点如我们所见,本来有很多等待执行的函数,我们不知道哪一个可以执行请求的时候就要一个一个去验证,于是出现了最开始的那个代码。而在职责链模式中,由于不知道链中的哪个节点可以处理你发出的请求,所以你只需把请求传递给第一个节点即可。

    使用了职责链模式之后,链中的节点对象可以灵活地拆分重组。增加或者删除一个节点,或者改变节点在链中的位置都是轻而易举的事情。这一点我们也已经看到,在上面的例子中,增加一种订单完全不需要改动其他订单函数中的代码。

    而且职责链模式还有一个优点,那就是可以手动指定起始节点,请求并不是非得从链中的第一个节点开始传递,当我们明确第一个节点并不具有执行能力时,我们可以从第二个或者更后面的节点开始传递,这样可以减少请求在链里面传递的次数。

    缺点,可能没有一个节点可以执行,请求会从链尾离开或者抛出一个错误。而且为了职责链而职责链可能因为过多没有必要的节点,带来性能方面的问题。

    总结

    职责链模式在js开发很容易被忽略,它结合无论是结合组合模式还是利用AOP的思想,都能发挥巨大作用。

  • 相关阅读:
    spring mvc注入配置文件里的属性
    spring mvc注入配置文件里的属性
    spring mvc注入配置文件里的属性
    ajaxFileUpload进行文件上传时,总是进入error
    ajaxFileUpload进行文件上传时,总是进入error
    ajaxFileUpload进行文件上传时,总是进入error
    配置quartz启动时就执行一次
    配置quartz启动时就执行一次
    JAVA遍历机制的性能的比较
    MyBatis中jdbcType与javaType对应表
  • 原文地址:https://www.cnblogs.com/grey-zhou/p/6374617.html
一二三 - 开发者的网上家园