策略模式中的策略就是一种算法或者业务规则,将这些策略作为函数进行封装,并向外提供统一的调用执行,本文给大家介绍 javascript 设计模式 -- 策略模式之输入验证,需要的朋友参考下
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算饭的客户.
先定义一个简单的输入表单:
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-">
- <style>
- .form{ width: px; height: px; #margin: px auto; } .form-item-label{ width:px;
- text-align: right; float: left; } .form-item-input{ float: left; } .form-item{
- width: % ; height: px; line-height: px; }
- </style>
- </head>
- <body>
- <div class='form'>
- <div class="form-item">
- <div class='form-item-label'>
- <span>
- 用户名:
- </span>
- </div>
- <div class='form-item-input'>
- <input id='userName' name='用户名' type="text">
- </div>
- </div>
- <div class="form-item">
- <div class='form-item-label'>
- <span>
- 密码:
- </span>
- </div>
- <div class='form-item-input'>
- <input id='password' name='密码' type="text">
- </div>
- </div>
- <div class="form-item">
- <div class='form-item-label'>
- <span>
- 确认密码:
- </span>
- </div>
- <div class='form-item-input'>
- <input id='repassword' name='密码确认' type="text">
- </div>
- </div>
- <div class="form-item">
- <div class='form-item-label'>
- <span>
- 邮箱:
- </span>
- </div>
- <div class='form-item-input'>
- <input id='mail' name='邮箱' type="text">
- </div>
- </div>
- </div>
- <br>
- <button id='submit'>
- 提交
- </button>
- <script type='text/javascript' src="../reference/jquery-...min.js">
- </script>
- </body>
- </html>
一般在页面上编辑信息后的提交动作中,都需要对输入的信息进行验证,会看到把很多负责 check 的代码写在提交函数中或者写在一个独立的 check 函数中。
比如像下面这样。
- $(document).ready(function(){
- $('#submit').bind('click', doSubmit);
- });
- function doSubmit(){
- var eleUserName = document.getElementById('userName');
- if(eleUserName.value === '') {
- alert('用户名不能为空');
- return;
- }
- if(eleUserName.length < 6) {
- alert('用户名长度不能少于6个字符');
- return;
- }
- if(eleUserName.length > 6) {
- alert('用户名长度不能多于20个字符');
- return;
- }
- }
这样的写法功能上肯定能满足要求,但是,会存在几个问题:
1. 如果我要在其他页面上使用,那就要将代码进行复制,所谓的复用就变成了复制,代码会存在大量重复。好一点的会把 check 代码分类整理封装,单还会存在较多的重复复制。
2. 如果我要增加一个输入验证,那么就要直接修改提交函数,该函数会显的臃肿,并且是破坏 "开闭" 原则的。
3. 如果修改了提交函数,就要将函数设计的测试全都覆盖一遍,因为,不知道何时就会发生误改或者未知的情况。
改造步骤:
1. 将每个验证逻辑看成是一个验证策略并封装成每个验证策略函数,函数参数保持一致,可以接受 dom 元素,被验证的值,错误消息,定制参数。
2. 定义验证器,可将验证策略函数导入,也可以添加。
3. 验证器提供验证方法,用于验证时的调用,其内部调用具体的验证策略函数。
4. 验证调用。
步骤 1.
把每一个 if 都看成一种校验的业务规则,把每种业务规则作为一个单独的策略函数,将所有的策略函数封装成一个策略对象。
- var validationStrategies = {
- isNoEmpty: function(element, errMsg, value) {
- if(value === '') {
- return this.buildInvalidObj(element, errMsg, value );
- }
- },
- minLength: function(element, errMsg, value, length) {
- if(value.length < length){
- return this.buildInvalidObj(element, errMsg, value);
- }
- },
- maxLength: function(element, errMsg, value, length) {
- if(value.length > length){
- return this.buildInvalidObj(element, errMsg, value);
- }
- },
- isMail: function(element, errMsg, value, length) {
- var reg = /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/;
- if(!reg.test(value)){
- return this.buildInvalidObj(element, errMsg, value);
- }
- }
- };
所有函数的参数的前 3 个都保持一致,而且是必须的,表示被验证的 DOM 元素,错误消息,被验证的值,第 4 个开始由函数自身的验证规则决定定制的参数,可有多个参数。
"buildInvalidObj" 方法只是把前 3 个参数打成一个错误对象进行返回,只要验证不通过就会返回这个错误对象。
根据依赖倒置原则,高层次的模块不应该依赖于低层次的模块,因此不能让验证的调用方直接使用。
通过验证器的方式进行封装和抽象。
步骤 2:
定义验证器,可以将所有验证策略导入其内,也可以单独添加验证策略函数。
- //输入验证器
- function InputValidators(){
- this.validators = [];
- this.strategies = {};
- }
- //从策略对象导入验证策略函数
- //参数:
- // strategies: 包含各种策略函数的对象
- InputValidators.prototype.importStrategies = function(strategies) {
- for(var strategyName in strategies) {
- this.addValidationStrategy(strategyName, strategies[strategyName]);
- }
- };
- //添加验证策略函数
- //参数:
- // name: 策略名称
- // strategy: 策略函数
- InputValidators.prototype.addValidationStrategy = function(name, strategy){
- this.strategies[name] = strategy;
- };
步骤 3:
添加验证方法,接受外部调用。
第一个参数 rule,设置成验证规则,比如 "minLength:6",通过下面的代码会生成对具体策略函数的调用,调用会压到缓存中,等待一起调用。
":6" 表示策略函数根据自身规则所定制的参数。
- //添加验证方法
- //参数:
- // rule: 验证策略字符串
- // element: 被验证的dom元素
- // errMsg: 验证失败时显示的提示信息
- // value: 被验证的值
- InputValidators.prototype.addValidator = function(rule, element, errMsg, value) {
- var that = this;
- var ruleElements = rule.split(":");
- this.validators.push(function() {
- var strategy = ruleElements.shift();
- var params = ruleElements;
- params.unshift(value);
- params.unshift(errMsg);
- params.unshift(element);
- return that.strategies[strategy].apply(that, params);
- });
- };
通过一个 check 函数来调用所有的验证。并将错误的结果进行返回。
- //开始验证
- InputValidators.prototype.check = function() {
- for (var i = 0,
- validator; validator = this.validators[i++];) {
- var result = validator();
- if (result) {
- return result;
- }
- }
- };
步骤 4:
在需要验证的地方,先 new 一个验证器对象。
- var validators = new InputValidators();
将包含验证策略函数的对象导入,或者单独添加验证策略函数。
- validators.importStrategies(validationStrategies);
- validators.addValidationStrategy('isEqual',
- function(element, errMsg, value1, value2) {
- if (value1 !== value2) {
- return this.buildInvalidObj(element, errMsg, value1);
- }
- });
可以看出,不同的验证策略我们可以预先封装进策略对象中,也可以根据实际情况即时添加。
然后通过添加验证方法将需要验证的策略,被验证的 dom 元素,错误消息,被验证的值添加进验证器中,这样避免了直接调用策略对象,降低了耦合性。
- var eleUserName = document.getElementById('userName');
- validators.addValidator('isNoEmpty', eleUserName, '用户名不能为空', eleUserName.value);
- validators.addValidator('minLength:6', eleUserName, '用户名的字符个数必须是6到20个', eleUserName.value);
- validators.addValidator('maxLength:20', eleUserName, '用户名的字符个数必须是6到20个', eleUserName.value);
- var elePassword = document.getElementById('password');
- validators.addValidator('isNoEmpty', elePassword, '密码不能为空', elePassword.value);
- validators.addValidator('minLength:6', elePassword, '密码的字符个数必须是6到20个', elePassword.value);
- validators.addValidator('maxLength:20', elePassword, '密码的字符个数必须是6到20个', elePassword.value);
- var eleRepassword = document.getElementById('repassword');
- validators.addValidator('isNoEmpty', eleRepassword, '确认密码不能为空', eleRepassword.value);
- validators.addValidator('minLength:6', eleRepassword, '确认密码的字符个数必须是6到20个', eleRepassword.value);
- validators.addValidator('maxLength:20', eleRepassword, '确认密码的字符个数必须是6到20个', eleRepassword.value);
- validators.addValidator('isEqual:' + elePassword.value, eleRepassword, '两次密码不一致', eleRepassword.value);
- var eleMail = document.getElementById('mail');
- validators.addValidator('isNoEmpty', eleMail, '邮箱不能为空', eleMail.value);
- validators.addValidator('isMail', eleMail, '邮箱不是一个有效的格式', eleMail.value);
调用验证器的 check 执行所有的验证。
- var result = validators.check();
- if(result){
- alert(result.errMsg);
- result.element.focus();
- result.element.select();
- return false;
- }
check 返回的是错误对象,我们可以在 check 后通过该对象统一地对 DOM 元素进行提示性操作,比如设置焦点,选中内容,或者为输入框外部包上一层红色的样式。
至此,可以看出通过策略模式的改在,输入验证时,我们只需要关心用哪个验证规则,采用什么样的提示性信息即可,不再暴露实现细节,方便调用,方便后续的扩展和组件化。
全部代码:
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <style>
- .form{ width: 400px; height: 200px; #margin: 0px auto; } .form-item-label{
- width:100px; text-align: right; float: left; } .form-item-input{ float:
- left; } .form-item{ width: 100% ; height: 50px; line-height: 50px; }
- </style>
- </head>
- <body>
- <div class='form'>
- <div class="form-item">
- <div class='form-item-label'>
- <span>
- 用户名:
- </span>
- </div>
- <div class='form-item-input'>
- <input id='userName' name='用户名' type="text">
- </div>
- </div>
- <div class="form-item">
- <div class='form-item-label'>
- <span>
- 密码:
- </span>
- </div>
- <div class='form-item-input'>
- <input id='password' name='密码' type="text">
- </div>
- </div>
- <div class="form-item">
- <div class='form-item-label'>
- <span>
- 确认密码:
- </span>
- </div>
- <div class='form-item-input'>
- <input id='repassword' name='密码确认' type="text">
- </div>
- </div>
- <div class="form-item">
- <div class='form-item-label'>
- <span>
- 邮箱:
- </span>
- </div>
- <div class='form-item-input'>
- <input id='mail' name='邮箱' type="text">
- </div>
- </div>
- </div>
- <br>
- <button id='submit'>
- 提交
- </button>
- <script type='text/javascript' src="../reference/jquery-1.11.3.min.js">
- </script>
- <script type='text/javascript'>
- $(document).ready(function() {
- $('#submit').bind('click', doSubmit);
- });
- function doSubmit() {
- var validators = new InputValidators();
- validators.importStrategies(validationStrategies);
- validators.addValidationStrategy('isEqual',
- function(element, errMsg, value1, value2) {
- if (value1 !== value2) {
- return this.buildInvalidObj(element, errMsg, value1);
- }
- });
- var eleUserName = document.getElementById('userName');
- validators.addValidator('isNoEmpty', eleUserName, '用户名不能为空', eleUserName.value);
- validators.addValidator('minLength:6', eleUserName, '用户名的字符个数必须是6到20个', eleUserName.value);
- validators.addValidator('maxLength:20', eleUserName, '用户名的字符个数必须是6到20个', eleUserName.value);
- var elePassword = document.getElementById('password');
- validators.addValidator('isNoEmpty', elePassword, '密码不能为空', elePassword.value);
- validators.addValidator('minLength:6', elePassword, '密码的字符个数必须是6到20个', elePassword.value);
- validators.addValidator('maxLength:20', elePassword, '密码的字符个数必须是6到20个', elePassword.value);
- var eleRepassword = document.getElementById('repassword');
- validators.addValidator('isNoEmpty', eleRepassword, '确认密码不能为空', eleRepassword.value);
- validators.addValidator('minLength:6', eleRepassword, '确认密码的字符个数必须是6到20个', eleRepassword.value);
- validators.addValidator('maxLength:20', eleRepassword, '确认密码的字符个数必须是6到20个', eleRepassword.value);
- validators.addValidator('isEqual:' + elePassword.value, eleRepassword, '两次密码不一致', eleRepassword.value);
- var eleMail = document.getElementById('mail');
- validators.addValidator('isNoEmpty', eleMail, '邮箱不能为空', eleMail.value);
- validators.addValidator('isMail', eleMail, '邮箱不是一个有效的格式', eleMail.value);
- var result = validators.check();
- if (result) {
- alert(result.errMsg);
- result.element.focus();
- result.element.select();
- return false;
- }
- alert('验证通过');
- }
- //输入验证器
- function InputValidators() {
- this.validators = [];
- this.strategies = {};
- //this.from(validationStrategies);
- }
- //添加验证方法
- //参数:
- // rule: 验证策略字符串
- // element: 被验证的dom元素
- // errMsg: 验证失败时显示的提示信息
- // value: 被验证的值
- InputValidators.prototype.addValidator = function(rule, element, errMsg, value) {
- var that = this;
- var ruleElements = rule.split(":");
- this.validators.push(function() {
- var strategy = ruleElements.shift();
- var params = ruleElements;
- params.unshift(value);
- params.unshift(errMsg);
- params.unshift(element);
- return that.strategies[strategy].apply(that, params);
- });
- };
- //添加验证策略函数
- //参数:
- // name: 策略名称
- // strategy: 策略函数
- InputValidators.prototype.addValidationStrategy = function(name, strategy) {
- this.strategies[name] = strategy;
- };
- //从策略对象导入验证策略函数
- //参数:
- // strategies: 包含各种策略函数的对象
- InputValidators.prototype.importStrategies = function(strategies) {
- for (var strategyName in strategies) {
- this.addValidationStrategy(strategyName, strategies[strategyName]);
- }
- };
- //验证失败时,将相关的错误信息打包返回
- //参数:
- // element: dom元素
- // errMsg: 验证失败时的提示消息
- // value: 被验证的值
- InputValidators.prototype.buildInvalidObj = function(element, errMsg, value) {
- return {
- 'value': value,
- 'element': element,
- 'errMsg': errMsg
- };
- };
- //开始验证
- InputValidators.prototype.check = function() {
- for (var i = 0,
- validator; validator = this.validators[i++];) {
- var result = validator();
- if (result) {
- return result;
- }
- }
- };
- //验证策略对象,包含默认的验证策略函数
- var validationStrategies = {
- isNoEmpty: function(element, errMsg, value) {
- if (value === '') {
- return this.buildInvalidObj(element, errMsg, value);
- }
- },
- minLength: function(element, errMsg, value, length) {
- if (value.length < length) {
- return this.buildInvalidObj(element, errMsg, value);
- }
- },
- maxLength: function(element, errMsg, value, length) {
- if (value.length > length) {
- return this.buildInvalidObj(element, errMsg, value);
- }
- },
- isMail: function(element, errMsg, value, length) {
- var reg = /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/;
- if (!reg.test(value)) {
- return this.buildInvalidObj(element, errMsg, value);
- }
- }
- };
- </script>
- </body>
- </html>
来源: