《javascript設計模式與開發實踐》閱讀筆記(13)—— 職責鏈模式

 

職責鏈模式

使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係,將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。es6

 

書裏的訂單的例子

假設咱們負責一個售賣手機的電商網站,通過分別交納500元定金和200元定金的兩輪預約(訂單已在此時生成),如今已經到了正式購買的階段。公司針對支付過定金的用戶有必定的優惠政策。在正式購買後,已經支付過500元定金的用戶會收到100元的商城優惠券,200元定金的用戶能夠收到50元的優惠券,而以前沒有支付定金的用戶只能進入普通購買模式,也就是沒有優惠券,且在庫存有限的狀況下不必定保證能買到。ajax

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優惠券

 

上面的代碼其實不用看完就已經能感覺到十分的繁瑣,把全部的邏輯都列出來使得函數變得十分臃腫。這種相似的代碼在策略模式中其實出現過,最後使用策略模式很好的消化了內部的條件分支,這裏的話咱們用職責鏈模式也能解決。app

 

使用職責鏈的思想重寫函數

思路:彼此委託,我能夠執行我就執行,我執行不了我就委託給別人執行,因此咱們把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元的訂單,那麼咱們就要改寫上面的函數。函數

能夠說,只要有點改動,不管是增長仍是刪除哪一環,咱們必須拆開這個鏈條才行,並且必須修改函數內部。性能

那這個問題的根源在哪裏,傳遞請求是職責鏈模式的根本,也是這個函數和上一個函數最大的區別所在,因此傳遞請求這一點是沒有問題的,咱們須要他們一個委託一個,那麼問題的所在就是委託函數之間耦合的太死,以致於改動必須深刻內部才行。因此,咱們要想辦法下降委託函數彼此間的耦合。網站

 

進一步修改函數

思路:想要解耦合,那麼就是用變量代替具體的函數,而後用一個對象安排每一個函數以後的委託函數。可是每一個函數後面的委託函數是不同的,因此不能使用相同的變量,可是若是使用不一樣的變量,其實質和如今並無多少區別。this

從委託這個字眼,咱們想到對象之間能夠很方便的調用方法,對象接收這個函數,併產生相應的屬性,包括當前的函數,和以後須要執行的委託函數。仔細一想,咱們的作法其實至關於擴展原來的函數,在不改動原來函數的基礎上,咱們想要實現委託,那就只有把函數變成參數來生成一個更爲全能的對象,而後讓這幾個功能更強的對象之間彼此交互。由於這些對象彼此相似,功能也相同,因此咱們須要一個模板來生成這些對象。同時爲了讓對象能夠意識到該委託別的對象了,函數的返回值應該作一個統一規定。spa

 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的思想,都能發揮巨大做用。

相關文章
相關標籤/搜索