JavaScript設計模式之職責鏈模式

定義

職責鏈模式的定義:使多個對象都有機會處理請求,從而避免了請求的發送者與多個接收者直接的耦合關係,將這些接收者鏈接成一條鏈,順着這條鏈傳遞該請求,直到找到能處理該請求的對象。javascript

應用

假設咱們負責一個售賣手機的網站,需求的定義是:通過分別繳納500元定金和200元定金的兩輪預訂,如今到了正式購買階段。公司對於交了定金的用戶有必定的優惠政策,規則以下:繳納500元定金的用戶能夠收到100元優惠券;納200元定金的用戶能夠收到50元優惠券;而沒有繳納定金的用戶進入普通購買模式,沒有優惠券,並且在庫存不足的狀況下,不必定能保證買獲得。下面開始設計幾個字段,解釋它們的含義:java

  • orderType:表示訂單類型,值爲1表示500元定金用戶,值爲2表示200元定金用戶,值爲3表示普通用戶。
  • pay:表示用戶是否支付定金,值爲布爾值true和false,就算用戶下了500元定金的訂單,可是若是沒有支付定金,那也會降級爲普通用戶購買模式。
  • stock:表示當前用戶普通購買的手機庫存數量,已經支付過定金的用戶不受限制。

下面把上面的需求用代碼實現:ajax

const order = function (orderType, pay, stock) {
  if (orderType === 1) {
    if (pay === true) {
      console.log('500元定金預購,獲得100元優惠券')
    } else {
      if (stock > 0) {
        console.log('普通用戶購買,無優惠券')
      } else {
        console.log('手機庫存不足')
      }
    } else if (orderType === 2) {
      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元優惠券'
複製代碼

雖然經過上面代碼咱們獲得了想要的結果,可是代碼難以閱讀,維護起來也很困難,若是須要修改需求,那代價無疑是巨大的。

使用職責鏈模式重構
下面咱們使用職責鏈模式重構,先把500元訂單、200元訂單以及普通購買拆分紅三個函數。代碼以下:app

function order500 (orderType, pay, stock) {
  if (orderType === 1 && pay === true) {
    console.log('500元定金預購,獲得100元優惠券')
  } else {
    order200(orderType, pay, stock)
  }
}

function order200 (orderType, pay, stock) {
  if (orderType === 2 && pay === true) {
    console.log('200元定金預購,獲得50元優惠券')
  } else {
    order200(orderType, pay, stock)
  }
}

function orderNormal (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元定金預購,獲得50元優惠券
order500(3, false, 500)  // 普通用戶購買,無優惠券
order500(3, false, 0)   // 手機庫存不足
複製代碼

能夠看到,重構後的代碼已經清晰不少,減小了大量的if-else嵌套,每一個函數的職責分明。可是還不夠,雖然咱們把大函數拆分紅了三個小函數,可是請求在鏈條中傳遞的順序很僵硬,傳遞請求的代碼跟業務代碼耦合在一塊兒,若是有一天要增長300元定金的預訂,那麼就要切斷以前的鏈條,修改訂單500函數的代碼,從新在500和200之間加一根新的鏈條,這違反了開放-封閉原則。

靈活可拆分的職責鏈節點
首先修改三個函數,若是某個節點不能處理請求,則返回一個特定的字符串「nextSuccessor」來表示請求須要繼續日後傳遞:異步

function order500 (orderType, pay, stock) {
  if (orderType === 1 && pay === true) {
    console.log('500元定金預購,獲得100元優惠券')
  } else {
    return 'nextSuccessor'
  }
}

function order200 (orderType, pay, stock) {
  if (orderType === 2 && pay === true) {
    console.log('200元定金預購,獲得50元優惠券')
  } else {
    return 'nextSuccessor'
  }
}

function orderNormal (orderType, pay, stock) {
  if (stock > 0) {
    console.log('普通用戶購買,無優惠券')
  } else {
    console.log('手機庫存不足')
  }
}
複製代碼

接下來須要定義一個Chain類將三個函數包裝進職責鏈節點:函數

class Chain {
  construct (fn) {
    this.fn = fn
    this.successor = null
  }

  setNextSuccessor (successor) {
    return this.successor = successor
  }

  passRequest () {
    const res = this.fn.apply(this, arguments)

    if (res === 'nextSuccessor') {
      return this.successor && this.successor.passRequest.apply(this.successor, arguments)
    }
    return res
  }
}

// 包裝三個訂單函數
const chainOrder500 = new Chain(order500)
const chainOrder200 = new Chain(order200)
const 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預約金的類型,只須要在鏈中增長一個節點:性能

function order300 () {
  // 省略代碼
}

const chainOrder300 = new Chain(order300)
chainOrder500.setNextSuccessor(chainOrder300)
chainOrder300.setNextSuccessor(chainOrder200)
複製代碼

這樣的修改簡單容易,徹底不用理會原來其它訂單的代碼。測試

異步的職責鏈

在上面的例子中,每一個節點函數都是同步返回一個特定值來表示是否把請求傳遞給下一個節點。可是在實際應用中,咱們常常會遇到一些異步的問題,好比要在某個節點中經過發起一個ajax異步請求,須要根據異步請求返回的結果才決定是否繼續傳遞請求,這時候咱們須要再添加一個函數,手動傳遞請求給職責鏈中的下一個節點:網站

class Chain {
  construct (fn) {
    this.fn = fn
    this.successor = null
  }

  setNextSuccessor (successor) {
    return this.successor = successor
  }

  next () {
    return this.successor && this.successor.passRequest.apply(this.successor, arguments)
  }

  passRequest () {
    const res = this.fn.apply(this, arguments)

    if (res === 'nextSuccessor') {
      return this.successor && this.successor.passRequest.apply(this.successor, arguments)
    }
    return res
  }
}
複製代碼

看一個異步使用的例子:ui

const fn1 = new Chain(function () {
  console.log(1)
  return 'nextSuccessor'
})

const fn1 = new Chain(function () {
  console.log(2)
  setTimeout(() => {
    this.next()
  }, 1000)
})

const fn3 = new Chain(function () {
  console.log(3)
})

fn1.setNextSuccessor(fn2).setNextSuccessor(fn3)
fn1.passRequest()
複製代碼

這樣咱們獲得了一個能夠處理異步狀況的職責鏈,異步的職責鏈加上命令模式,能夠很方便地建立一個異步ajax隊列庫。

用AOP實現職責鏈

前面的例子咱們是利用了一個Chain類來把普通函數包裝成職責鏈的節點,利用JavaScript函數式的特性,咱們能夠實現一種更加方便地方法來建立職責鏈:

Function.prototype.after = function (fn) {
  const self = this
  return function () {
    const res = self.apply(this, arguments)
    if (res === 'nextSuccessor') {
      return fn.apply(this, arguments)
    }
    return res
  }
}

const order = order500.after(order200).after(orderNormal)
order(1, true, 500)   // 500元定金預購,獲得100元優惠券
order(2, true, 500)   // 200元定金預購,獲得50元優惠券
order(3, true, 500)   // 普通用戶購買,無優惠券
order(1, false, 0)    // 手機庫存不足
複製代碼

使用AOP方式實現職責鏈簡單又巧妙,但這種方式把函數疊加在一塊兒,也增長了函數的做用域,若是鏈條太長,也會有必定的性能問題。

總結

職責鏈模式的最大優勢就是解耦了請求發送者和多個請求接收者之間的關係。其次,使用了職責鏈模式以後,鏈中的節點對象能夠靈活地拆分重組,增長、刪除和修改節點在鏈中的位置都是很容易地事。它還有一個優勢就是,能夠手動地指定起始節點,請求並非必定要從鏈中的第一個節點開始傳遞。 固然,這種模式並不是沒有缺點,首先咱們不能保證某個請求必定會被鏈中的節點處理,因此須要在鏈尾增長一個保底的接受者處理這種狀況。另外職責鏈模式使得程序中多了一些節點對象,可能在某一次請求傳遞中,大部分節點並無起做用,因此過長的職責鏈會帶來性能的損耗。 在JavaScript中。不管是做用鏈、原型鏈,仍是DOM節點中的事件冒泡,咱們都能從中找到職責鏈的影子。

相關文章
相關標籤/搜索