設計模式之策略模式
在web項目中,表單的驗證和提交是我們經常開發的功能之一。下面我們來看一下一般情況下我們如何驗證一個用戶的註冊。
需求:
註冊需要用戶名,密碼,手機號碼,郵箱
所有選項不能為空
密碼要長度不能少於8位,並且不能全部為數字
手機號碼要驗證符合當前手機格式
郵箱也要驗證格式
來看我們最直觀的實現功能的代碼:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>策略模式</title> </head> <scripttype="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就是一個策略管理類,他收集了驗證整個過程所需要的驗證方法,最後提供接口調用收集到的所有方法
最後就是客戶端調用方法,進行驗證
好處:
策略模式定義了一系列算法,從概念上來說,所有的這些算法都是做相同的事情,只是實現不同,他可以以相同的方式調用所有的方法,減少了各種算法類與使用算法類之間的耦合。
從另外一個層面上來說,單獨定義算法類,也方便了單元測試,因為可以通過自己的算法進行單獨測試。
設計模式之策略模式