javascript設計模式 之 10 職責鏈模式

zhaoyezi發表於2018-06-04

1 職責鏈模式的定義

使多個物件都有機會處理請求,從而避免請求的傳送者和接收者的耦合關係。將這些物件連在一條鏈上,並沿這條鏈傳遞該請求,直到有一個物件處理它為止。我們把這些物件稱為鏈中的節點。

javascript設計模式 之 10 職責鏈模式

2 現實中的職責鏈

我們希望實現一個流程,商場預定手機:

  • 預定定金500並已付款, 購買時列印500 元定金預購, 得到 100 優惠券
  • 預定定金200並已付款, 購買時列印200 元定金預購, 得到 50 優惠券
  • 否則是普通購買或者預定沒有付款:stock有庫存普通購買, 無優惠券,stock === 0時手機庫存不足
var order = function( orderType, pay, stock ){
if ( orderType === 1  && pay === true) { // 500 元定金購買模式
    console.log( '500 元定金預購, 得到 100 優惠券' );
} else if ( orderType === 2 && pay === true) { // 200 元定金購買模式
   console.log( '200 元定金預購, 得到 50 優惠券' );
} else if ( orderType === 3 || pay === false) {
    if ( stock > 0 ){
        console.log( '普通購買, 無優惠券' );
    } else {
        console.log( '手機庫存不足' );
    }
}
};
order( 1, true, 500); // 輸出: 500 元定金預購, 得到 100 優惠券
複製程式碼

4 使用職責鏈重構程式碼

  • 分別表示 3 種購買模式的節點函式,我們約定,如果某個節點不能處理請求,則返回一個特定的字串 'nextSuccessor'來表示該請求需要繼續往後面傳遞。
  • 把函式包裝進職責鏈節點,我們定義一個建構函式 Chain,在 new Chain 的時候傳遞的引數即為需要被包裝的函式, 同時它還擁有一個例項屬性 this.successor,表示在鏈中的下一個節點。
// 500預定
var order500 = function(orderType, pay, stock) {
    if ( orderType === 1  && pay === true) { // 500 元定金購買模式
        console.log( '500 元定金預購, 得到 100 優惠券' );
    } else {
        return 'nextSuccessor'; // // 我不知道下一個節點是誰,反正把請求往後面傳遞
    }
}
// 200預定
var order200 = function(orderType, pay, stock) {
    if ( orderType === 2  && pay === true) { // 500 元定金購買模式
        console.log( '100 元定金預購, 得到 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.nextSuccessor = null;
}
Chain.prototype.setNextSuccessor = function(fn) {
    return this.nextSuccessor = fn;
};
Chain.prototype.passRequest = function() {
    var ret = this.fn.apply(this, arguments);
    if (ret === 'nextSuccessor') {
        return this.nextSuccessor && this.nextSuccessor.passRequest.apply(this.nextSuccessor, arguments);
    }
    return ret;
}

複製程式碼

作用域鏈已經建立好了,下面就開始來呼叫:

var chain500 = new Chain(order500);
var chain200 = new Chain(order200);
var chainNormal = new Chain(orderNormal);
chain500.setNextSuccessor(chain200).setNextSuccessor(chainNormal);
chain500.passRequest(3, false, 20);
複製程式碼

通過改進,我們可以自由靈活地增加、移除和修改鏈中的節點順序,假如某天網站運營人員又想出了支援 300 元定金購買,那我們就在該鏈中增加一個節點即可.

var order200 = function(orderType, pay, stock) {
    if ( orderType === 4  && pay === true) { // 300 元定金購買模式
        console.log( '300 元定金預購, 得到 80 優惠券' );
    } else {
        return 'nextSuccessor'; // // 我不知道下一個節點是誰,反正把請求往後面傳遞
    }
}
var chan300 = new Chian(order300);
chain500.setNextSuccessor(chan300);
chan300.setNextSuccessor(chain200);
複製程式碼

5 使用AOP實現職責鏈

在高階函式部分我們使用了AOP.我們改寫函式的Function.prototype.after函式,使其前一個函式返回nextSuccessor時,我們繼續將請求傳遞給下一個函式。

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;
    }
}
order500.after(order200).after(orderNormal)(2, true, 20);
複製程式碼

6 使用職責鏈獲取檔案上傳物件

var iEUpload = function() {
    try{
        return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上傳控制元件
    } catch(e) {
        return false;
    }
}

var flashUpload = function() {
    // if (supportFlash()) { // supportFlash 函式未提供
    if (parseInt(Math.random() * 10) % 2 === 0) { 
        var str = '<object type="application/x-shockwave-flash"></object>';
        return document.body.append(str);
    } 
    return false;
}

var getFormUpload = function() {
    var str = '<input name="file" type="file"/>'; // 表單上傳
    return $( str ).appendTo( $('body') );
}

Function.prototype.after = function(fn) {
    var self = this;
    return function() {
        var ret = self.apply(this, arguments);
        if (!ret) {
            return fn.apply(this, arguments);
        }
        return ret;
    }
}

// 使用職責鏈呼叫
console.log(iEUpload.after(flashUpload).after(flashUpload)());
複製程式碼

小結

職責鏈運用好了,可以幫助我們很好管理程式碼,降低發起請求的物件和處理物件之間的耦合性。職責鏈中的節點數量和順序是自由變化的。無論在作用域鏈還是原型鏈,還是DOM節點的冒泡事件,都能夠看到職責鏈的影子。

  • 首次使用職責鏈模式後,鏈中的節點可以自由組合,增加或刪除一個節點都是輕而易舉的(我覺得就是單向連結串列)
  • 可以手動指定起始節點,不一定必須從第一個節點開始傳遞。
  • 弊端:不能保證某個請求一定會被鏈中的節點處理
  • 弊端:職責鏈模式會多出節點物件,可能在某一次的請求中大部分節點沒有起到實質性的作用。它們唯一的功能就是向下傳遞。從效能考慮,需要避免過長的職責鏈帶來的效能損耗。

使用策略模式完成相同的功能

我覺得使用策略模式顯得更簡潔。

function buy(stock) {
    if (stock > 0) {
        console.log( '普通購買, 無優惠券' );
    } else {
        console.log( '手機庫存不足' );
    }
}

var stratege= {
    1: function() {
        console.log('500 元定金預購, 得到 100 優惠券');
    },
    5: function () {
        console.log('500 元定金預購, 得到 50 優惠券')
    }
};

// context 類
function order(type, pay, stock) {
    if (stratege && pay) {
        stratege[type]();
    } else {
       buy(stock)
    }
}
order(1, true, 100)
複製程式碼

相關文章