设计模式之策略模式
在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就是一个策略管理类,他收集了验证整个过程所需要的验证方法,最后提供接口调用收集到的所有方法
最后就是客户端调用方法,进行验证
好处:
策略模式定义了一系列算法,从概念上来说,所有的这些算法都是做相同的事情,只是实现不同,他可以以相同的方式调用所有的方法,减少了各种算法类与使用算法类之间的耦合。
从另外一个层面上来说,单独定义算法类,也方便了单元测试,因为可以通过自己的算法进行单独测试。