设计模式之策略模式

web项目中,表单的验证和提交是我们经常开发的功能之一。下面我们来看一下一般情况下我们如何验证一个用户的注册。

需求

注册需要用户名,密码,手机号码,邮箱

所有选项不能为空

密码要长度不能少于8位,并且不能全部为数字

手机号码要验证符合当前手机格式

邮箱也要验证格式

来看我们最直观的实现功能的代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>策略模式</title>
  </head>
  <script type="text/javascript" src="index.js"></script>
  <body>
  <form id="registerform">
      <div>
          <label for="username">用户名</label>
          <input type="text" id="username" name="username">
      </div>
      <div>
          <label for="pwd">密码</label>
          <input type="password" id="pwd" name="pwd">
      </div>
      <div>
          <label for="tel">手机号码</label>
          <input type="text" id="tel" name="tel">
      </div>
      <div>
          <label for="email">邮箱</label>
          <input type="email" id="email" name="email">
      </div>
      <button id="submit-btn" type="button">注册</button>
  </form>
  </body>
  <script type="text/javascript" src="index.js"></script>
  <script type="text/javascript">
      var registerForm = document.querySelector('#registerform');
      document.getElementById('submit-btn').addEventListener('click', function(e){
          var username = registerForm.username.value,
              pwd = registerForm.pwd.value,
              tel = registerForm.tel.value,
              email = registerForm.email.value;
              if(username === ''){
                  alert('用户名不能为空');
                  return false;
              }

              if(pwd === ''){
                  alert('用户名密码不能为空');
                  return false;
              }

              if(pwd.length < 8){
                  alert('用户名密码必须超过8位数');
                  return false;
              }

              if(/^[0-9]*$/.test(pwd)){
                  alert('用户名密码不能全部为数字');
                  return false;
              }

              if(tel === ''){
                  alert('手机号码不能为空');
                  return false;
              }

              if(!/^1(3|5|7|8|9)[0-9]{9}$/.test(tel)){
                  alert('输入的手机号码不正确,请重新输入');
                  return false;
              }

              if(email.length === ''){
                  alert('邮箱不能为空');
                  return false;
              }

              if(!/^\w+([+-.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(email)){
                  alert('输入的邮箱不正确,请重新输入');
                  return false;
              }

          // 表单提交
          // $.ajax({
          //     url: '/path/to/file',
          //     type: 'default GET (Other values: POST)',
          //     dataType: 'default: Intelligent Guess (Other values: xml, json, script, or html)',
          //     data: {param1: 'value1'},
          // })
          // .done(function() {
          //     console.log("success");
          // })
          
      }, false)
  </script>
</html>

上面的代码,最直观完成了我们所需要的验证功能,没有任何上的逻辑错误。但是存在了很多问题。

  最直观的是代码里面有很多条件判断语句,这些语句覆盖了所有的验证规则,验证规则的增加会使得判断语句越来越多,不方便代码的维护

  算法的复用性差,可以看到里面验证输入不能为空的判断语句就写了4次。还有如果这时候另外一个表单也需要类似的验证,我们很可能将这段验证复制到另外的表单中。

  函数缺乏弹性。如果增加了一种新的校验规则,或者想要把密码的长度校验从8改成6,我们都必须深入提交绑定的函数的内部实现,这是违反了开放-封闭原则的。

  最后还有一点,我们提交的函数里面即包含了验证的功能,也包含了数据提交的功能,违反了单一职责的原则。

  对于以上的弊端,我们可以采用设计模式中的策略模式来解决。

策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。

下面我们来看通过策略模式进行优化后的实现

var strategies = {
          isNonEmpty: function(value, errorMsg) {
              return value === '' ? errorMsg : '';
          },
          isMinLength: function(value, length, errorMsg) {
              return value.length < length ? errorMsg : '';
          },
          isFullNum: function(value, errorMsg) {
              return /^[0-9]*$/.test(value) ? errorMsg : '';
          },
          isMobile: function(value, errorMsg) {
              return !/^1(3|5|7|8|9)[0-9]{9}$/.test(tel) ? errorMsg : '';
          },
          isEmail: function(value, errorMsg) {
              return !/^\w+([+-.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(tel) ? errorMsg : '';
          }

      }

      /**策略管理类*/
      var Validator = function() {
          this.methods = [];
      }

      Validator.prototype.add = function(dom, rules) {
          var self = this;
          for (var i = 0, length = rules.length; i < length; ++i) {
              var rule = rules[i];
              (function(rule) {
                  self.methods.push(function() {
                      var strategyArry = rule['strategy'].split(':'),
                      errorMsg = rule['errorMsg'];
                      var methodName = strategyArry.shift();
                      strategyArry.unshift(dom.value);
                      strategyArry.push(errorMsg);
                      return strategies[methodName].apply(dom, strategyArry);
                  })
              })(rule);
          }
      };

      Validator.prototype.start = function() {
          console.log(this.methods);
          for (var i = 0, length = this.methods.length; i < length; ++i) {
              var validatorFunc = this.methods[i];
              var errorMsg = validatorFunc();
              if (errorMsg) {
                  return errorMsg;
              }
          }
      }

      var registerForm = document.querySelector('#registerform');
      var registerValidator = new Validator();
      registerValidator.add(registerForm.username, [{
          'strategy': 'isNonEmpty',
          'errorMsg': '用户名不能为空'
      }])

      registerValidator.add(registerForm.pwd, [{
          'strategy': 'isNonEmpty',
          'errorMsg': '密码不能为空'
      }, {
          'strategy': 'isMinLength:8',
          'errorMsg': '用户名密码必须超过8位数'
      }])

      registerValidator.add(registerForm.tel, [{
          'strategy': 'isNonEmpty',
          'errorMsg': '手机号不能为空'
      }, {
          'strategy': 'isMobile',
          'errorMsg': '请输入正确的手机号码'
      }])

      registerValidator.add(registerForm.email, [{
          'strategy': 'isNonEmpty',
          'errorMsg': '邮箱不能为空'
      }, {
          'strategy': 'isMobile',
          'errorMsg': '请输入正确的电子邮箱'
      }])


      document.getElementById('submit-btn').addEventListener('click', function(e) {
          var errorMsg = registerValidator.start();
          if (errorMsg) {
              alert(errorMsg);
              return false;
          }
      }, false)

通过优化后的实现,我们代码就显得很清晰。

1、首先验证的具体算法和验证的过程分离了

2、验证策略变成了可配置,通过验证策略的组装可以完成不同的验证过程,大大提高了代码的可复用性

那么如何实现策略模式?实现策略模式需要需要以下3个步骤

需要一个策略对象

策略对象是由一组验证的算法组成的,上面strategies就是我们定义的策略对象

抽象策略角色

即编写一个策略管理类

策略管理类就是管理整个过程所需要的策略,比如上面的例子,Validator就是一个策略管理类,他收集了验证整个过程所需要的验证方法,最后提供接口调用收集到的所有方法

最后就是客户端调用方法,进行验证

好处:

策略模式定义了一系列算法,从概念上来说,所有的这些算法都是做相同的事情,只是实现不同,他可以以相同的方式调用所有的方法,减少了各种算法类与使用算法类之间的耦合。

从另外一个层面上来说,单独定义算法类,也方便了单元测试,因为可以通过自己的算法进行单独测试。

 

posted @ 2017-05-22 17:15  CaiBoBo  阅读(432)  评论(0编辑  收藏  举报