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

来源:http://www.cnblogs.com/grey-zhou/archive/2017/02/07/6374617.html
-Advertisement-
Play Games

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

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 什麼是微服務 一些協同工作的小而自治的服務 微服務的優點 技術異構性 彈性 擴展性 簡化部署 對可替代性的優化 微服務的原則 原則舉例 ...
  • Netty 提供非同步的、事件驅動的網路應用程式框架和工具,用以快速開發高性能、高可靠性的網路伺服器和客戶端程式 本文對Netty做了一個總體概覽 ...
  • 今天我們來講一下適配器模式。下麵,我們描述一個場景: 不管是籃球隊還是足球隊,都會有外援的,在隊內訓練與安排戰術的時候,外援可能聽不懂漢語的,那麼,他們怎麼交流呢,這就需要一個翻譯了。其實,這個翻譯就起到了一個適配器的效果。 何為適配器模式:將一個介面轉換成為客戶希望的另外一個介面,使得原本由於介面 ...
  • 閱讀目錄 前言 回顧 本地的一致性 領域事件發佈出現異常 訂閱者處理出現異常 結語 一、前言 上篇中我們初步運用了領域事件,其中還有一些問題我們沒有解決,所以實現是不健壯的,下麵先來回顧一下。 二、回顧 先貼一下上篇中的遺留的問題: 不知道大家有沒有發現這裡代碼上的一個問題,就是DomainEven ...
  • 今天我們來將狀態模式,首先,我們來描述下麵一個場景: 一、案例: 在工作過程中,根據時間段的不同,我們工作的狀態也有所不同,下麵,我們用簡單的控制台應用程式,來實現一下這個場景。 客戶端 二、演繹 1、第一步演繹 看到上面用代碼描述的場景,對於我們學了好多設計模式的小伙伴來講,是不是顯得特別的挫,最 ...
  • 什麼是微服務? 微服務存在多種定義。 如果搜索 Internet,會發現許多有用的資源,這些資源提供了自己的觀點和定義。 但在微服務的以下大部分特性上,已廣泛達成共識: 封裝客戶方案或業務方案。 你要解決什麼問題? 由小型工程團隊開發。 使用任何編程語言編寫並使用任何框架。 由獨立控製版本、部署及縮 ...
  • 1.module.export可以公共方法,也可以公共變數 2. 調用公共方法的時候,寫預設參數的時候可以這樣: function wxReq(method, url, header, data, successCb, failCb, fail, that) {} 調用的時候可以 wxReq("GE ...
  • 原生JavaScript實現AJAX、JSONP 相信大多數前端開發者在需要與後端進行數據交互時,為了方便快捷,都會選擇JQuery中封裝的AJAX方法,但是有些時候,我們只需要JQuery的AJAX請求方法,而其他的功能用到的很少,這顯然是沒必要的。 其實,原生JavaScript實現AJAX並不 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...