使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係,將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。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 );
小結:咱們把手動的修改,變成了經過對象控制,這樣只須要幾個句子就能很方便的修改業務邏輯,不用去改動任何源碼。
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的思想,都能發揮巨大做用。