Skip to content

策略模式(Strategy)

策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
解析:就是定义一系列的算法,把它们各自封装成策略类,算法被封装在策略类内部的方法里。在客户对 Context 发起请求的时候,Context 总是把请求委托给这些策略对象中间的某一个进行计算。
使用场景

  • 校验器
  • 缓动动画
  • 可替换的计算

将不变的部分和变化的部分分隔开是每个设计模式的主题。

策略模式的目的就是将算法的使用与算法的实现分离开来。

比如
在我们不使用设计模式的情况下,总会这么写:

javascript
const calculateBonus = function(performanceLevel, salary){
  if(performanceLevel === 'S') {
    return salary * 4;
  }
  if(performanceLevel === 'A') {
    return salary * 3;
  }
  if(performanceLevel === 'B') {
    return salary * 2;
  }
}
calculateBonus('B', 20000);
calculateBonus('S', 6000);

缺点

  1. 包含太多 if-else 语句。
  2. 缺乏弹性,如果需要增加多个等级的时候,需要深入函数内部实现。
  3. 算法复用性差。


使用策略模式下

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

const calculateBonus = function(level, salary){
  return strategies[level](salary);
}

calculateBonus('S', 20000);

若要增加其他等级,直接修改 strategies 即可,调用完全不用改。

例如:使用策略模式实现一个表单检验器validator

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<form action="/xxx" id="resForm" method="post">
  姓名:<input type="text" name="username" placeholder="请输入你的姓名">
  密码:<input type="password" name="pwd" placeholder="请输入你的密码">
  手机号码:<input type="text" name="phone" placeholder="请输入你的手机号码">
  <button>提交</button>
</form>


<script>
  const resForm = document.getElementById('resForm')
  resForm.addEventListener('submit', function () {
    const errMsg = validatFunc()
    alert(errMsg)
    if (errMsg) {
      return false
    }
  })

  const validationRules = {
    isNonEmpty: function (value, errMsg) {
      if (value === '') {
        return errMsg
      }
    },
    minLength: function (value, length, errMsg) {
      if (value.length < length) {
        return errMsg
      }
    },
    isPhone: function (value, errMsg) {
      const phoneRE = /^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/;
      if (phoneRE.test(value)) {
        return errMsg
      }
    }
  }

  class Validator {
    constructor() {
      this.cache = [];
    }

    add(dom, rules) {
      rules.forEach(rule => {
        let arr = rule.type.split(':');
        const errMsg = rule.errMsg;

        this.cache.push(() => {
          const type = arr.shift();
          arr.unshift(dom.value);
          arr.push(errMsg);

          return validationRules[type].apply(dom, arr);
        })
      })
    }

    start() {
      for (let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
        const errorMsg = validatorFunc();
        if (errorMsg) {
          return errorMsg;
        }
      }
    }
  }

  const validatFunc = function () {
    const validator = new Validator();

    validator.add(resForm.username, [{
      type: 'isNonEmpty',
      errMsg: '姓名不能为空'
    }, {
      type: 'minLength:2',
      errMsg: '姓名不能少于2位'
    }])

    validator.add(resForm.pwd, [{
      type: 'isNonEmpty',
      errMsg: '密码不能为空'
    }, {
      type: 'minLength:2',
      errMsg: '密码不能少于6位'
    }])

    validator.add(resForm.phone, [{
      type: 'isPhone',
      errMsg: '请输入正确的手机号码'
    }])

    const errMsg = validator.start();
    return errMsg
  }
</script>
</body>
</html>

Released under the MIT License.