JavaScript設計模式之策略模式

定義

定義一系列算法,把它們一個個封裝成策略類,具體的算法封裝在策略類的內部方法裏,而且使這些策略類能夠互相替換。一個基於策略模式的設計至少由兩部分組成,第一部分是一組策略類,每一個策略類裏封裝了具體的算法。第二部分是環境類Context,Context主要接受客戶的請求,而後把請求委託給某一個策略類。javascript

應用

下面主要經過兩個具體的案例來介紹策略類的使用。java

使用策略模式計算獎金

不少公司都設有年終獎,並且年終獎通常跟員工的月工資基數和績效有關。若是績效爲S,發4個月年終獎;績效A發3個月;績效爲B,發2個月。用代碼計算獎金以下:算法

const calculateBonus = function (performanceLevel, salary) {
  if (performanceLevel === 'S') {
    return salary * 4
  }
  if (performanceLevel === 'A') {
    return salary * 3
  }
  if (performanceLevel === 'B') {
    return salary * 2
  }
}
複製代碼

用簡單的代碼配合if語句就能實現該功能,可是卻有不少的缺點。設計模式

  • calculateBonus函數龐大,有不少的if語句。
  • calculateBonus缺少彈性,若是再增長一種新的績效等級C,或者修改其中的一種績效的計算方式,則必須深刻calculateBonus的內部實現,違法了開閉原則。
  • 複用性差,若是其它地方須要重用到這些計算獎金的算法,只能經過複製和粘貼

下面使用策略模式重構代碼。第一步,定義策略類:app

// S
class PerformanceS {
  construct (salary) {
    this.salary = salary
  }

  calculate () {
    return this.salary * 4
  }
}

// A
class PerformanceA {
  construct (salary) {
    this.salary = salary
  }

  calculate () {
    return this.salary * 3
  }
}

// B
class PerformanceB {
  construct (salary) {
    this.salary = salary
  }

  calculate () {
    return this.salary * 2
  }
}
複製代碼

第二步,定義環境類,Bonus:dom

class Bonus {
  construct (salary) {
    this.salary = salary
  }

  setStrategy (Strategy) {
    this.strategy = new Strategy(this.salary)
  }

  getBonus () {
    return this.strategy.calculate()
  }
}
複製代碼

使用:函數

const bonus = new Bonus(7000)
bonus.setStrategy(PerformanceS)
bonus.getBonus() // 28000
複製代碼

重構完以後,咱們發現代碼更加清晰,每一個類的職責也更加鮮明。ui

JavaScript版本的設計模式

上面咱們是用面向對象的方式來實現策略模式,在JavaScript中,函數也是對象,因此更加直接的作法是:this

const strategies = {
  'S': function (salary) {
    return salary * 4
  },
  'A': function (salary) {
    return salary * 3
  },
  'B': function (salary) {
    return salary * 2
  }
}

const calculateBonus = function (performanceLevel, salary) {
  return strategies[performanceLevel](salary)
}
複製代碼

表單校驗

策略模式中的策略類主要是用來封裝算法的,可是若是隻封裝算法,有點大材小用。在實際開發中,咱們能夠把算法定義變得更廣,好比一系列的業務規則,這些業務規則的目標一致,而且能夠被相互替換使用。下面經過策略模式來編寫一個表單校驗的例子。

需求是這樣的,咱們在編寫一個註冊的頁面,在點擊註冊按鈕以前,有以下幾條校驗規則:spa

  • 用戶名長度不能爲空。
  • 密碼長度不能少於6位。
  • 手機號必須符合格式。

第一個版本

const registerForm = document.getElementById('registerForm')

registerForm.onsubmit = function () {
  if (registerForm.userName.value === '') {
    alert('用戶名不能爲空')
    return
  }
  if (registerForm.password.value.length < 6) {
    alert('用戶密碼不能少於6位')
    return
  }
  if (!/(^1[3|5|7|8][0-9]{9}$)/.test(registerForm.phone.value)) {
    alert('用戶手機格式不正確')
    return
  }
}
複製代碼

它的缺點跟計算獎金的第一個版本差很少,缺少彈性,違反開閉原則,複用性差。

用策略模式重構
定義校驗規則的策略:

const strategies= {
  isNonEmpty (value, errMsg) {
    if (value === '') {
      return errMsg
    }
  },
  minLength (value, length, errMsg) {
    if (value.length < length) {
      return errMsg
    }
  },
  isMobile (value, errMsg) {
    if (!/(^1[3|5|7|8][0-9]{9}$)/.test(value)) {
      return errMsg
    }
  }
}
複製代碼

先看具體怎麼使用Validator:

const validatorFunc = function () {
  const validator = new Validator()
  // 添加校驗規則
  validator.add(registerForm.userName, 'isNonEmpty', '用戶名不能爲空')
  validator.add(registerForm.password, 'minLength:6', '用戶密碼不能少於6位')
  validator.add(registerForm.mobile, 'isMobile', '用戶手機格式不正確')

  const errMsg = validator.start()  // 得到校驗結果
  return erMsg
}

const registerForm = document.getElementById('registerForm')
registerForm.onsubmit = function () {
  const errMsg = validatorFunc()

  if (errMsg) {
    alert(errMsg)
    return
  }
}
複製代碼

定義環境類Validator類:

class Validator {
  static rules = []
  
  add (dom, rule, errMsg) {
    const ary = rule.split(':')
    this.rules.push(function () {
      const rule = ary.shift()
      ary.unshift(dom.value)
      ary.push(errMsg)
      return strategies[rule].apply(dom, ary)
    })
  }

  start () {
    for (let i = 0; i < this.rules.length; i++) {
      const validatorFunc = this.rules[i]
      const errMsg = validatorFunc()
      if (errMsg) {
        return errMsg
      }
    }
  }
}
複製代碼

經過策略模式重構以後,只須要經過配置就能夠完成表單的校驗,這些校驗規則還能夠在任何地方複用。若是須要進行修改,好比改校驗規則或者提示,也是成本很低的。

策略模式的優缺點

優勢:

  • 策略模式利用組合、委託和多態等技術和思想,避免多重if-else語句。
  • 提供了對開發-關閉原則的完美支持,將算法封裝在策略類中,易於修改,易於擴展。
  • 因爲策略模式是將算法封裝在策略類中,因此這些算法能夠在項目中的任何地方複用。
  • 利用組合和委託讓Context擁有執行算法的能力,也是繼承的一種替代方案。

缺點:

  • 首先,使用策略模式會在程序中增長不少的策略類,增長了項目的代碼。
  • 其次,使用策略模式,必須瞭解全部的strategy類,這樣才能知道使用哪一個策略類。因此策略類必須向客戶暴露它的全部實現,違反了最少知識原則。
相關文章
相關標籤/搜索