1. 程式人生 > >設計模式之策略模式

設計模式之策略模式

條件 cti round ces 配置 urn 表單提交 spa 成了

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就是一個策略管理類,他收集了驗證整個過程所需要的驗證方法,最後提供接口調用收集到的所有方法

最後就是客戶端調用方法,進行驗證

好處:

策略模式定義了一系列算法,從概念上來說,所有的這些算法都是做相同的事情,只是實現不同,他可以以相同的方式調用所有的方法,減少了各種算法類與使用算法類之間的耦合。

從另外一個層面上來說,單獨定義算法類,也方便了單元測試,因為可以通過自己的算法進行單獨測試。

設計模式之策略模式