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类,这样才能知道使用哪一个策略类。因此策略类必须向客户暴露它的全部实现,违反了最少知识原则。
相关文章
相关标签/搜索