職責鏈模式 使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係,將這些對象連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個對象處理它為止。 書里的訂單的例子 假設我們負責一個售賣手機的電商網站,經過分別交納500元定金和200元定金的兩輪預定(訂單已在此時生成),現在已經到了正式購 ...
職責鏈模式
使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係,將這些對象連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個對象處理它為止。
書里的訂單的例子
假設我們負責一個售賣手機的電商網站,經過分別交納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的思想,都能發揮巨大作用。