天天看點

使用職責鍊模式來重構你的代碼

在這裡我們先想象一個場景,假設我們是一個售賣手機的網站,前期出過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 優惠券
複制代碼           

這段代碼可以得到我們想要的結果,但是這段代碼的可維護性和可閱讀性都很差,下面我們可以使用職責鍊模式來嘗試改進它。

顧名思義,責任鍊模式(Chain of Responsibility Pattern)為請求建立了一個接收者對象的鍊。這種模式給予請求的類型,對請求的發送者和接收者進行解耦。在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個對象不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。

問題解決: 職責鍊上的處理者負責處理請求,客戶隻需要将請求發送到職責鍊上即可,無須關心請求的處理細節和請求的傳遞,是以職責鍊将請求的發送者和請求的處理者解耦了。

代碼如下:

// 500 元訂單
var order500 = function (orderType, pay, stock) {
  if (orderType === 1 && pay === true) {
    console.log('500 元定金預購, 得到 100 優惠券')
  } else {
    order200(orderType, pay, stock) // 将請求傳遞給 200 元訂單
  }
}
// 200 元訂單
var order200 = function (orderType, pay, stock) {
  if (orderType === 2 && pay === true) {
    console.log('200 元定金預購, 得到 50 優惠券')
  } else {
    orderNormal(orderType, pay, stock) // 将請求傳遞給普通訂單
  }
}
// 普通購買訂單
var orderNormal = function (orderType, pay, stock) {
  if (stock > 0) {
    console.log('普通購買, 無優惠券')
  } else {
    console.log('手機庫存不足')
  }
}
// 測試結果:
order500(1, true, 500) // 輸出:500 元定金預購, 得到 100 優惠券
order500(1, false, 500) // 輸出:普通購買, 無優惠券
order500(2, true, 500) // 輸出:200 元定金預購, 得到 500 優惠券
order500(3, false, 500) // 輸出:普通購買, 無優惠券
order500(3, false, 0) // 輸出:手機庫存不足
複制代碼           

這個函數和前面的order函數執行結果完全一緻,但是代碼結構已經清晰了很多,我們避免了一些備援的ifelse,并且将一個體積龐大的函數拆分成邏輯更加清晰的小函數。但是這樣拆分明顯也有一個問題,請求的傳遞是強耦合的,即500order和200的order是強耦合的,如果我們需要添加一個400元的訂單,那麼我又需要去修改函數内部,那麼有沒有更加靈活的方式呢?答案是有的。

我們可以用一個職責鍊類Chain和特定的傳遞請求的字元串'nextSuccessor'來拆分職責鍊節點

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

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

這樣我們可以靈活的增加,修改,删除節點順序,如果突然來一個需求說需要支援300元的定金, 那我們直接添加一個節點即可

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

使用職責鍊模式,我們可以隻增加一個節點并且調整順序即可就,并不會像第一個函數一樣需要修改訂單函數代碼,因為在實際場景中,業務邏輯遠遠比這個複雜,出錯率也比這個例子高得多。

異步的職責鍊

在上文中,我們使用同步傳回的'nextSuccess'表示需要傳遞請求給下一個節點,但是實際開發中肯定會遇到異步場景,這時候我們需要手動的觸發一個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() 
複制代碼           

職責鍊模式優點

  1. 降低了請求發送者和請求接受者的耦合關系,我們不用關心它走了哪一個請求,我們都交給第一個節點處理就行
  2. 增強給對象指派職責的靈活性。通過改變鍊内的成員或者調動它們的次序,允許動态地新增或者删除責任。
  3. 增加新的請求處理類很友善。

缺點

  1. 不能保證請求一定被接收, 除非我們在鍊尾處理了這種請求。
  2. 系統性能将受到一定影響,在請求過程中,大部分節點僅僅做了請求傳遞的作用,并且系統會增加額外的職責鍊類

原文釋出時間為:2018年6月9日

原文作者:jacintoface

本文來源:

掘金

如需轉載請聯系原作者

繼續閱讀