什么是策略模式

策略模式就是将一系列算法封装起来,并使它们相互之间可以替换。被封装起来的算法具有独立性,外部不可改变其特性。
今天采用两个场景,第一个场景是商城搞促销活动,第二个场景是表单验证。那么下面直接进入正题。

商城促销案例

假如今天是双十一,商城有促销,促销方案如下:
1、满100减5
2、满200减15
3、满300减30
4、满400减50

通用写法是这样的

  1. function full100(price) {
  2. return price - 5;
  3. }
  4. function full200(price) {
  5. return price - 15;
  6. }
  7. function full300(price) {
  8. return price - 30;
  9. }
  10. function full400(price) {
  11. return price - 50;
  12. }
  13. function calculate (type, price) {
  14. if (type == 'full100') {
  15. return full100(price)
  16. }
  17. if (type == 'full200') {
  18. return full200(price)
  19. }
  20. if (type == 'full300') {
  21. return full300(price)
  22. }
  23. if (type == 'full400') {
  24. return full400(price)
  25. }
  26. }

从代码上看确实没啥毛病,但是如果情况有变呢?岂不是每添加一个方案就会写一个方法和一个if判断。
显然,这种方式扩展性不高,需要改正,我把它封装到一个对象中,每个算法都封装为一个方法,再写一个调用计算的方法给外部调用,然后只需要给它传参不就行了么。
再考虑全面一点,如果我的方案有变化呢,我不想每次去添加方法,而是给它一个接口自己去完成促销方案的添加。

优化后的写法

  1. var countPrice = {
  2. returnPrice: {
  3. full100: function (price) {
  4. return price - 5
  5. },
  6. full200: function (price) {
  7. return price - 15
  8. },
  9. full300: function (price) {
  10. return price - 30
  11. },
  12. full400: function (price) {
  13. return price - 50
  14. }
  15. },
  16. getPirce: function (type, money) {
  17. return this.returnPrice[type] ? this.returnPrice[type](money) : money;
  18. },
  19. addRule: function (type, discount) {
  20. this.returnPrice[type] = function (price) {
  21. return price - discount;
  22. }
  23. }
  24. }

怎么样,代码是不是少了很多,来做一下测试,假如用户选择了满300减30的优惠方案,调用如下:

  1. console.log(countPrice.getPirce('full300',399))
  2. // 输出 369

现在增加促销方案,新增一个满500减100的方案

  1. countPrice.addRule('full500', 100);
  2. console.log(countPrice.getPirce('full500',599))
  3. // 输出 499

表单验证案例

开发一个用户注册页面,其中表单包含了用户名、密码、确认密码以及手机号码,要求所有数据都不为空,密码至少6位,确认密码必须与密码相等。
准备工作,先把html和css写好

  1. <style>
  2. .flex {
  3. display: flex;
  4. justify-content: space-between;
  5. width: 300px;
  6. margin: 10px;
  7. }
  8. </style>
  9. <form action="" id="form">
  10. <div class="flex">姓名:<input type="text" id="username" /></div>
  11. <div class="flex">密码:<input type="password" id="password1" /></div>
  12. <div class="flex">确认密码:<input type="password" id="password2" /></div>
  13. <div class="flex">手机号:<input type="text" id="phone" /></div>
  14. <div class="flex"><input type="submit" value="提交" /></div>
  15. </form>

直接做表单验证

  1. var formData = document.getElementById('form')
  2. formData.onsubmit = function () {
  3. var name = this.username.value
  4. var pwd1 = this.password1.value
  5. var pwd2 = this.password2.value
  6. var tel = this.phone.value
  7. if (name.replace(/(^\s*)|(\s*$)/g, '') === '') {
  8. alert('用户名不能为空')
  9. return false
  10. }
  11. if (pwd1.replace(/(^\s*)|(\s*$)/g, '') === '') {
  12. alert('密码不能为空')
  13. return false
  14. }
  15. if (pwd2.replace(/(^\s*)|(\s*$)/g, '') === '') {
  16. alert('确认密码不能为空')
  17. return false
  18. }
  19. if (pwd2 !== pwd1) {
  20. alert('确认密码与原密码不相同!')
  21. return false
  22. }
  23. if (tel.replace(/(^\s*)|(\s*$)/g, '') === '') {
  24. alert('手机号码不能为空')
  25. return false
  26. }
  27. if (!/^1[3,4,5,7,8,9][0-9]\d{8}$/.test(tel)) {
  28. alert('手机号码格式不正确')
  29. return false
  30. }
  31. alert('注册成功')
  32. }

直接做表单验证整个逻辑对于新手来说会很直观,没那么多弯弯绕绕的,但是缺点也明显,4个表单数据,就用了6个if去判断,如果这个页面不是用户注册,而是某个管理页面中的表单,包含了十多个表单数据呢,那只会更多。需要验证的越多,代码就会越来越臃肿,当需要进行很多验证的时候,可以考虑策略模式

封装成独立方法

第一步,把 if else 不用分支处理的事情抽象成一个个不同、独立方法

  1. function Validate() {}
  2. // 定义在原型链,方便调用
  3. Validate.prototype.rules = {
  4. // 是否手机号
  5. isMobile: function (str) {
  6. var rule = /^1[3,4,5,7,8,9][0-9]\d{8}$/
  7. return rule.test(str)
  8. },
  9. // 是否必填
  10. isRequired: function (str) {
  11. // 除去首尾空格
  12. var value = str.replace(/(^\s*)|(\s*$)/g, '')
  13. return value !== ''
  14. },
  15. // 最小长度
  16. minLength: function (str, length) {
  17. var strLength = str.length
  18. return strLength >= length
  19. },
  20. // 是否相等
  21. isEqual: function () {
  22. // 可以接收多个参数比较
  23. var args = Array.prototype.slice.call(arguments)
  24. // 取首项与后面所有的项比较,如果每个都相等,就返回true
  25. var equal = args.every(function (value) {
  26. return value === args[0]
  27. })
  28. return equal
  29. }
  30. }

第二步,实现一个对外暴露的接口,或者说方法,供外部调用

  1. Validate.prototype.test = function (rules) {
  2. var v = this
  3. var valid // 保存校验结果
  4. for (var key in rules) {
  5. // 遍历校验规则对象
  6. for (var i = 0; i < rules[key].length; i++) {
  7. // 遍历每一个字段的校验规则
  8. var ruleName = rules[key][i].rule // 获取每一个校验规则的规则名
  9. var value = rules[key][i].value // 获取每一个校验规则的校验值
  10. if (!Array.isArray(value)) {
  11. // 统一校验值为数组类型
  12. value = new Array(value)
  13. }
  14. var result = v.rules[ruleName].apply(this, value) // 调用校验规则方法进行校验
  15. if (!result) {
  16. // 如果校验不通过,就获取校验结果信息,并立即跳出循环不再执行,节约消耗
  17. valid = {
  18. errValue: key,
  19. errMsg: rules[key][i].message
  20. }
  21. break
  22. }
  23. }
  24. if (valid) {
  25. // 如果有了校验结果,代表存在不通过的字段,则立即停止循环,节约消耗
  26. break
  27. }
  28. }
  29. return valid // 把校验结果返回出去
  30. }

这个时候外部环境就可以调用了,外部环境要知道调用的格式,就是说要知道怎么用

  1. var formData = document.getElementById('form')
  2. formData.onsubmit = function () {
  3. event.preventDefault() //阻止元素默认行为
  4. console.log(this.username.value)
  5. var validator = new Validate()
  6. var result = validator.test({
  7. username: [
  8. {
  9. rule: 'isRequired',
  10. value: this.username.value,
  11. message: '用户名不能为空!'
  12. }
  13. ],
  14. password1: [
  15. {
  16. rule: 'isRequired',
  17. value: this.password1.value,
  18. message: '密码不能为空!'
  19. },
  20. {
  21. rule: 'minLength',
  22. value: [this.password1.value, 6],
  23. message: '密码长度不能小于6个字符!'
  24. }
  25. ],
  26. password2: [
  27. {
  28. rule: 'isRequired',
  29. value: this.password2.value,
  30. message: '确认密码不能为空!'
  31. },
  32. {
  33. rule: 'minLength',
  34. value: [this.password2.value, 6],
  35. message: '确认密码长度不能小于6个字符!'
  36. },
  37. {
  38. rule: 'isEqual',
  39. value: [this.password2.value, this.password1.value],
  40. message: '确认密码与原密码不相同!'
  41. }
  42. ],
  43. isMobile: [
  44. {
  45. rule: 'isRequired',
  46. value: this.phone.value,
  47. message: '手机号不能为空!'
  48. },
  49. { rule: 'isMobile', value: this.phone.value, message: '手机号格式不正确!' }
  50. ]
  51. })
  52. if (result) {
  53. console.log(result)
  54. } else {
  55. console.log('校验通过')
  56. }
  57. }

可以看出:优点有目共睹的,将一个个算法封装起来,提高代码复用率,减少代码冗余;策略模式可看作为if/else判断的另一种表现形式,在达到相同目的的同时,极大的减少了代码量以及代码维护成本。