《JavaScript設計模式與開發實踐》模式篇(10)—— 職責鏈模式

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

故事背景

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

代碼實現(未使用職責鏈模式)

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 ){ 
        if ( pay === true ){ // 200 元定金購買模式
            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 優惠券
複製代碼

重構思路

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

代碼重構(使用職責鏈模式)

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.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; 
};
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 );    // 輸出:手機庫存不足
複製代碼

經過改進,咱們能夠自由靈活地增長、移除和修改鏈中的節點順序,假如某天網站運營人員 又想出了支持 300 元定金購買,那咱們就在該鏈中增長一個節點便可app

var order300 = function(){
 // 具體實現略
};
chainOrder300= new Chain( order300 ); 
chainOrder500.setNextSuccessor( chainOrder300); chainOrder300.setNextSuccessor( chainOrder200);
複製代碼

異步的職責鏈

在現實開發中,咱們常常會遇到一些異步的問題,好比咱們要在 節點函數中發起一個 ajax 異步請求,異步請求返回的結果才能決定是否繼續在職責鏈中 passRequest。 這時候讓節點函數同步返回"nextSuccessor"已經沒有意義了,因此要給 Chain 類再增長一個原型方法 Chain.prototype.next,表示手動傳遞請求給職責鏈中的下一個節點異步

Chain.prototype.next= function(){
    return this.successor && this.successor.passRequest.apply( this.successor, arguments );
};
/* 異步職責鏈 */
var fn1 = new Chain(function(){
   console.log( 1 );
   return 'nextSuccessor';
});
var fn2 = new Chain(function(){ 
    console.log( 2 );
    var self = this; 
    setTimeout(function(){
        self.next(); 
    }, 1000 );
});
var fn3 = new Chain(function(){
    console.log( 3 );
});
fn1.setNextSuccessor( fn2 ).setNextSuccessor( fn3 ); 
fn1.passRequest();
複製代碼

如今咱們獲得了一個特殊的鏈條,請求在鏈中的節點裏傳遞,但節點有權利決定何時把 請求交給下一個節點。能夠想象,異步的職責鏈加上命令模式(把 ajax 請求封裝成命令對象),咱們能夠很方便地建立一個異步 ajax 隊列庫。函數

用AOP實現職責鏈

Function.prototype.after = function( fn ){ 
    var self = this;
    return function(){
        var ret = self.apply( this, arguments ); 
        if ( ret === 'nextSuccessor' ){
            return fn.apply( this, arguments ); 
        }
        return ret; 
    }
};
var order = order500yuan.after( order200yuan ).after( orderNormal );
order( 1, true, 500 );    // 輸出:500 元定金預購,獲得 100 優惠券 
order( 2, true, 500 );    // 輸出:200 元定金預購,獲得 50 優惠券 
order( 1, false, 500 );   // 輸出:普通購買,無優惠券
複製代碼

用 AOP 來實現職責鏈既簡單又巧妙,但這種把函數疊在一塊兒的方式,同時也疊加了函數的 做用域,若是鏈條太長的話,也會對性能有較大的影響post

小結

在 JavaScript 開發中,職責鏈模式是最容易被忽視的模式之一。實際上只要運用得當,職責鏈模式能夠很好地幫助咱們管理代碼,下降發起請求的對象和處理請求的對象之間的耦合性。職責鏈中的節點數量和順序是能夠自由變化的,咱們能夠在運行時決定鏈中包含哪些節點。性能

系列文章:

《JavaScript設計模式與開發實踐》最全知識點彙總大全網站

相關文章
相關標籤/搜索