js之職責鏈模式

職責鏈模式的定義是:使多個對象都有機會處理請求,從而避免請求的發送者接收者之間的耦合關係,將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象能處理它爲止。
職責鏈模式的名字非常形象,一系列可能會處理請求的對象被連接成一條鏈,請求在這些對象之間依次傳遞,直到遇到一個可以處理它的對象,我們把這些對象稱爲鏈中的節點。


level01:

假設我們負責一個售賣手機的電商網站,經過分別交納 500元定金和 200元定金的兩輪預定後(訂單已在此時生成),現在已經到了正式購買的階段。

公司針對支付過定金的用戶有一定的優惠政策。在正式購買後,已經支付過 500元定金的用戶會收到 100元的商城優惠券,200元定金的用戶可以收到 50元的優惠券,而之前沒有支付定金的用戶只能進入普通購買模式,也就是沒有優惠券,且在庫存有限的情況下不一定保證能買到。
 orderType :表示訂單類型(定金用戶或者普通購買用戶), code 的值爲 1的時候是 500元
定金用戶,爲 2的時候是 200元定金用戶,爲 3的時候是普通購買用戶。
 pay :表示用戶是否已經支付定金,值爲 true 或者 false , 雖然用戶已經下過 500元定金的
訂單,但如果他一直沒有支付定金,現在只能降級進入普通購買模式。
 stock :表示當前用於普通購買的手機庫存數量,已經支付過 500 元或者 200 元定金的用
戶不受此限制。
下面我們把這個流程寫成代碼:

var order = function( orderType, pay, stock ){
            if ( orderType === 1 ){ // 500 元定金購買模式
                if ( pay === true ){ // 已支付定金
                    console.log( '500 元定金預購, 得到 100 優惠券' );
                }else{ // 未支付定金,降級到普通購買模式
                    if ( stock > 0 ){ // 用於普通購買的手機還有庫存
                        console.log( '普通購買, 無優惠券' );
                    }else{
                        console.log( '手機庫存不足' );
                    }
                }
            }
            else if ( orderType === 2 ){ // 200 元定金購買模式
                if ( pay === true ){
                    console.log( '200 元定金預購, 得到 50 優惠券' );
                }else{
                    if ( stock > 0 ){
                        console.log( '普通購買, 無優惠券' );
                    }else{
                        console.log( '手機庫存不足' );
                    }
                }
            }
            else if ( orderType === 3 ){
                if ( stock > 0 ){
                    console.log( '普通購買, 無優惠券' );
                }else{
                    console.log( '手機庫存不足' );
                }
            }
        };
        order( 1 , true, 500); // 輸出: 500 元定金預購, 得到 100 優惠券

雖然我們得到了意料中的運行結果,但這遠遠算不上一段值得誇獎的代碼。 order 函數不僅巨大到難以閱讀,而且需要經常進行修改。雖然目前項目能正常運行,但接下來的維護工作無疑是個夢魘。恐怕只有最“新手”的程序員纔會寫出這樣的代碼。


實際開發中的職責鏈模式level02:

現在我們採用職責鏈模式重構這段代碼,先把 500 元訂單、200 元訂單以及普通購買分成 3個函數。
接下來把 orderType 、 pay 、 stock 這 3個字段當作參數傳遞給 500元訂單函數,如果該函數不符合處理條件,則把這個請求傳遞給後面的 200元訂單函數,如果 200元訂單函數依然不能處理該請求,則繼續傳遞請求給普通購買函數,代碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        var order500 = function( orderType, pay, stock ){
            if ( orderType === 1 && pay === true ){
                console.log( '500 元定金預購,得到 100 優惠券' );
            }else{
                return 'nextSuccessor'; // 我不知道下一個節點是誰,反正把請求往後面傳遞
            }
        };
        var order200 = function( orderType, pay, stock ){
            if ( orderType === 2 && pay === true ){
                console.log( '200 元定金預購,得到 50 優惠券' );
            }else{
                return 'nextSuccessor'; // 我不知道下一個節點是誰,反正把請求往後面傳遞
            }
        };
        var orderNormal = function( orderType, pay, stock ){
            if ( stock > 0 ){
                console.log( '普通購買,無優惠券' );
            }else{
                console.log( '手機庫存不足' );
            }
        };
        var Chain = function( fn ){
            this.fn = fn;
            this.successor = null;
        };
        Chain.prototype.setNextSuccessor = function( successor ){//指定在鏈中的下一個節點
            return this.successor = successor;
        };
        Chain.prototype.passRequest = function(){//傳遞請求給某個節點
            var ret = this.fn.apply( this, arguments );
            if ( ret === 'nextSuccessor' ){
                return this.successor && this.successor.passRequest.apply( this.successor, arguments );
            }
            return ret;
        };
//      現在我們把 3個訂單函數分別包裝成職責鏈的節點:
        var chainOrder500 = new Chain( order500 );
        var chainOrder200 = new Chain( order200 );
        var chainOrderNormal = new Chain( orderNormal );
//      然後指定節點在職責鏈中的順序:
        chainOrder500.setNextSuccessor( chainOrder200 );
        chainOrder200.setNextSuccessor( chainOrderNormal );
//      最後把請求傳遞給第一個節點:
        chainOrder500.passRequest( 1, true, 500 ); // 輸出:500 元定金預購,得到 100 優惠券
        chainOrder500.passRequest( 2, true, 500 ); // 輸出:200 元定金預購,得到 50 優惠券
        chainOrder500.passRequest( 3, true, 500 ); // 輸出:普通購買,無優惠券
        chainOrder500.passRequest( 1, false, 0 ); // 輸出:手機庫存不足
    </script>
</body>
</html>

可以看到,執行結果和前面那個巨大的 order 函數完全一樣,但是代碼的結構已經清晰了很多,我們把一個大函數拆分了 3個小函數,去掉了許多嵌套的條件分支語句。

目前已經有了不小的進步,但我們不會滿足於此,雖然已經把大函數拆分成了互不影響的 3個小函數,但可以看到,請求在鏈條傳遞中的順序非常僵硬,傳遞請求的代碼被耦合在了業務函數之中:

var order500 = function( orderType, pay, stock ){
            if ( orderType === 1 && pay === true ){
                console.log( '500 元定金預購, 得到 100 優惠券' );
            }else{
                order200( orderType, pay, stock );
                // order200 和 order500 耦合在一起
            }
        };

這依然是違反開放封閉原則的,如果有天我們要增加 300 元預訂或者去掉 200 元預訂,意味着就必須改動這些業務函數部。就像一根環環相扣打了死結的鏈條,如果要增加、拆除或者移動一個節點,就必須得先砸爛這根鏈條。


靈活可拆分的職責鏈節點level03:

本節我們採用一種更靈活的方式,來改進上面的職責鏈模式,目標是讓鏈中的各個節點可以靈活拆分和重組。

首先需要改寫一下分別表示 3種購買模式的節點函數,我們約定,如果某個節點不能處理請求,則返回一個特定的字符串 ‘nextSuccessor’ 來表示該請求需要繼續往後面傳遞:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        var order500 = function( orderType, pay, stock ){
            if ( orderType === 1 && pay === true ){
                console.log( '500 元定金預購,得到 100 優惠券' );
            }else{
                return 'nextSuccessor'; // 我不知道下一個節點是誰,反正把請求往後面傳遞
            }
        };
        var order200 = function( orderType, pay, stock ){
            if ( orderType === 2 && pay === true ){
                console.log( '200 元定金預購,得到 50 優惠券' );
            }else{
                return 'nextSuccessor'; // 我不知道下一個節點是誰,反正把請求往後面傳遞
            }
        };
        var orderNormal = function( orderType, pay, stock ){
            if ( stock > 0 ){
                console.log( '普通購買,無優惠券' );
            }else{
                console.log( '手機庫存不足' );
            }
        };
        /*接下來需要把函數包裝進職責鏈節點,我們定義一個構造函數 Chain ,在 new  Chain 的時候傳
        遞的參數即爲需要被包裝的函數, 同時它還擁有一個實例屬性 this.successor ,表示在鏈中的下
        一個節點。*/
//        此外 Chain 的 prototype 中還有兩個函數,它們的作用如下所示:
        // Chain.prototype.setNextSuccessor 指定在鏈中的下一個節點
        // Chain.prototype.passRequest 傳遞請求給某個節點
        var Chain = function( fn ){
            this.fn = fn;
            this.successor = null;
        };
        Chain.prototype.setNextSuccessor = function( successor ){//指定在鏈中的下一個節點
            return this.successor = successor;
        };
        Chain.prototype.passRequest = function(){//傳遞請求給某個節點
            var ret = this.fn.apply( this, arguments );
            if ( ret === 'nextSuccessor' ){
                return this.successor && this.successor.passRequest.apply( this.successor, arguments );
            }
            return ret;
        };
//      現在我們把 3個訂單函數分別包裝成職責鏈的節點:
        var chainOrder500 = new Chain( order500 );
        var chainOrder200 = new Chain( order200 );
        var chainOrderNormal = new Chain( orderNormal );
//      然後指定節點在職責鏈中的順序:
        chainOrder500.setNextSuccessor( chainOrder200 );
        chainOrder200.setNextSuccessor( chainOrderNormal );
//      最後把請求傳遞給第一個節點:
        chainOrder500.passRequest( 1, true, 500 ); // 輸出:500 元定金預購,得到 100 優惠券
        chainOrder500.passRequest( 2, true, 500 ); // 輸出:200 元定金預購,得到 50 優惠券
        chainOrder500.passRequest( 3, true, 500 ); // 輸出:普通購買,無優惠券
        chainOrder500.passRequest( 1, false, 0 ); // 輸出:手機庫存不足
    </script>
</body>
</html>

異步的職責鏈(即何時跳轉可以自定義)level04:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        var order01 = function(){
            console.log( 1 );
            return 'nextSuccessor';
        };
        var order02 = function(){
            console.log( 2 );
            var self = this;
            setTimeout(function(){
                self.next();
            }, 1000 );
        };
        var order03 = function(){
            console.log( 3 );
        };

        var Chain = function( fn ){
            this.fn = fn;
            this.successor = null;
        };
        Chain.prototype.setNextSuccessor = function( successor ){//指定在鏈中的下一個節點
            return this.successor = successor;
        };
        Chain.prototype.passRequest = function(){//傳遞請求給某個節點
            var ret = this.fn.apply( this, arguments );
            if ( ret === 'nextSuccessor' ){
                return this.successor && this.successor.passRequest.apply( this.successor, arguments );
            }
            return ret;
        };
        Chain.prototype.next= function(){
            return this.successor && this.successor.passRequest.apply( this.successor, arguments );
        };
//      現在我們把 3個訂單函數分別包裝成職責鏈的節點:
        var fn1 = new Chain( order01 );
        var fn2 = new Chain( order02 );
        var fn3 = new Chain( order03 );
//      然後指定節點在職責鏈中的順序:
        fn1.setNextSuccessor( fn2 ).setNextSuccessor( fn3 );
//      最後把請求傳遞給第一個節點:
        fn1.passRequest();
    </script>
</body>
</html>

職責鏈模式的優缺點

職責鏈模式使得程序中多了一些節點對象,可能在某一次的請求傳遞過程中,大部分節點並沒有起到實質性的作用,它們的作用僅僅是讓請求傳遞下去,從性能方面考慮,我們要避免過長的職責鏈帶來的性能損耗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章