Skip to content
文档章节

策略模式

背景

假设我们有一个表单,如下所示,需要在表单提交之前验证表单各项数据, 我们该如何处理呢?

html
<form action="http://liuzunkun.com" method="post" id="registerform">
  <label for="name">账户 </label>
  <input id="name" type="text" />
  <label for="password">口令 </label>
  <input id="password" type="password" />
  <label for="tel">手机号 </label>
  <input id="tel" type="tel" />
  <button type="submit">注册系统</button>
  <div id="errmsg"></div>
</form>

一个常用的处理代码书写方式是

js
const registerform = document.getElementById('registerform');

registerform.onsubmit = function (event) {
  event.preventDefault();

  if (!registerform.name.value || registerform.name.value.length < 6) {
    alert('请输入正确账户名');
    return;
  }

  if (!registerform.password.value || registerform.name.value.length < 6) {
    alert('请输入正确账户密码');
    return;
  }
  const mobileReg = /^(13[0-9]|14[5-9]|15[0-3,5-9]|16[6]|17[0-8]|18[0-9]|19[8,9])\d{8}$/;
  if (!mobileReg.test(egisterform.tel.value)) {
    alert('请输入正确手机号');
    return;
  }
  // 之后的逻辑
};

上面这种写法能满足需求。缺点是有很多判断逻辑,代码不够简洁。策略模式能够很好满足需求。

原理

策略模式将代码执行分为两个部分:

  1. 将具体执行算法拆分为各个策略类执行
  2. 环境类 context 接收客户请求,并将请求委托给合适的策略类执行

比如上面的需求中

  1. 定义一个 Validator Context 来接收请求
  2. 定个各种策略类执行的方案

代码说明

  1. 定义不同的策略执行算法
js
const strategies = {
  isNotEmpty: function (value, errMsg) {
    if (!value) return errMsg;
  },

  minLength: function (value, length, errMsg) {
    if (!value || value.length < length) return errMsg;
  },

  isMobile: function (value, errMsg) {
    const mobileReg = /^(13[0-9]|14[5-9]|15[0-3,5-9]|16[6]|17[0-8]|18[0-9]|19[8,9])\d{8}$/;

    if (!mobileReg.test(value)) return errMsg;
  },
};
  1. Context 接收客户请求

这里我们定义了一个 Validator 对象类来接收请求

js
// 验证类接收表单项的验证请求
Validator.prototype.add = function (elem, rules = []) {};
// 验证类开始执行各种策略算法
Validator.prototype.start = function () {};
  1. 根据验证算法请求,对表单发起不同的执行策略
js
function validateFn() {
  const validator = new Validator();

  validator.add(registerform.name, [
    {
      strategy: 'isNotEmpty',
      errMsg: '账户不能为空',
    },
    {
      strategy: 'minLength:6',
      errMsg: '账户名长度不能<6',
    },
  ]);

  validator.add(registerform.password, [
    {
      strategy: 'isNotEmpty',
      errMsg: '口令不能为空',
    },
    {
      strategy: 'minLength:6',
      errMsg: '口令名长度不能<6',
    },
  ]);

  validator.add(registerform.tel, [
    {
      strategy: 'isNotEmpty',
      errMsg: '手机号不能为空',
    },
    {
      strategy: 'isMobile',
      errMsg: '请输入正确的手机号',
    },
  ]);

  return validator.start();
}
  1. 接收执行算法后,根据不同情形给与委托策略
js
Validator.prototype.add = function (elem, rules = []) {
  let self = this;

  for (let i = 0; i < rules.length; i++) {
    (function (rule) {
      // 根据冒号传递参数
      let strategyArr = rule.strategy.split(':');
      let errMsg = rule.errMsg;

      self.cache.push(function () {
        // 策略名称
        let strategy = strategyArr.shift();

        strategyArr.unshift(elem.value);
        strategyArr.push(errMsg);

        // 分配策略算法
        return strategies[strategy].apply(elem, strategyArr);
      });
    })(rules[i]);
  }
};

代码如下

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>strategy</title>
  </head>
  <body>
    <form action="http://liuzunkun.com" method="post" id="registerform">
      <label for="name">账户 </label>
      <input id="name" type="text" />
      <label for="password">口令 </label>
      <input id="password" type="password" />
      <label for="tel">手机号 </label>
      <input id="tel" type="tel" />
      <button type="submit">注册系统</button>
      <div id="errmsg"></div>
    </form>

    <script>
      const strategies = {
        isNotEmpty: function (value, errMsg) {
          if (!value) return errMsg;
        },

        minLength: function (value, length, errMsg) {
          if (!value || value.length < length) return errMsg;
        },

        isMobile: function (value, errMsg) {
          const mobileReg = /^(13[0-9]|14[5-9]|15[0-3,5-9]|16[6]|17[0-8]|18[0-9]|19[8,9])\d{8}$/;

          console.log({
            value,
            result: mobileReg.test(value),
          });
          if (!mobileReg.test(value)) return errMsg;
        },
      };

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

      Validator.prototype.add = function (elem, rules = []) {
        let self = this;

        for (let i = 0; i < rules.length; i++) {
          (function (rule) {
            let strategyArr = rule.strategy.split(':');
            let errMsg = rule.errMsg;

            self.cache.push(function () {
              let strategy = strategyArr.shift();

              strategyArr.unshift(elem.value);
              strategyArr.push(errMsg);

              console.log({ strategy, strategyArr });

              return strategies[strategy].apply(elem, strategyArr);
            });
          })(rules[i]);
        }
      };
      Validator.prototype.start = function () {
        for (let i = 0; i < this.cache.length; i++) {
          let validateFn = this.cache[i];
          let errMsg = validateFn();
          if (errMsg) return errMsg;
        }
      };

      const registerform = document.getElementById('registerform');

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

        validator.add(registerform.name, [
          {
            strategy: 'isNotEmpty',
            errMsg: '账户不能为空',
          },
          {
            strategy: 'minLength:6',
            errMsg: '账户名长度不能<6',
          },
        ]);

        validator.add(registerform.password, [
          {
            strategy: 'isNotEmpty',
            errMsg: '口令不能为空',
          },
          {
            strategy: 'minLength:6',
            errMsg: '口令名长度不能<6',
          },
        ]);

        validator.add(registerform.tel, [
          {
            strategy: 'isNotEmpty',
            errMsg: '手机号不能为空',
          },
          {
            strategy: 'isMobile',
            errMsg: '请输入正确的手机号',
          },
        ]);

        return validator.start();
      }

      registerform.onsubmit = function (event) {
        event.preventDefault();

        let errMsg = validateFn();

        if (errMsg) {
          console.error(`验证失败`, errMsg);
          document.getElementById('errmsg').innerHTML = errMsg;
          return false;
        }

        document.getElementById('errmsg').innerHTML = '';
      };
    </script>
  </body>
</html>

参考

  1. JavaScript 设计模式与开发实践