介绍
策略模式的意义是定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。此模式让算法的变化不会影响到使用算法的客户。
实现
举一个例子,比如我们做数据合法性校验,一般是通过swich来实现,或者通过if语句来实现,如果校验规则多了的话,那么代码的扩展性和维护性就很差了,而且进行单元测试就越来越复杂,代码如下:
var validator = { validate: function(value,type) { switch(type) { case 'isNonEmpty': return true case 'isNumber'true; case 'isAlphaNum'default; } } } alert(validator.validate('123','isNonEmpty'))
怎么避免上面代码的弊端呢,我们可以使用策略模式把相同的工作代码封装成不同的验证类,我们只需要通过传递不同的名称来调用不同的验证类方法(也即是不同的算法),实现代码如下:
{ types: { // 存放验证规则 isNonEmpty: { validate: (value) { return value !== '' },instructions: '传入的值不能为空' },isNumber: { validate: return !isNaN(value); },instructions: '传入的值不是数字'return !/[^a-z0-9]/i.test(value) },instructions: '传入的值只能是数字或者字母,不能是特殊字符' } },config: {}, 需要验证的类型 messages: [], 存放错误信息 validate: function(data) { 传入的data 为key-value的键值对 var i,type,checker,resultOk; this.messages = []; 首先清空错误信息 for(i in data) { if(data.hasOwnProperty(i)) { 判断i不是原型上的属性 type = this.config[i]; 获取验证类型 if(!type) { 没有当前校验类型直接跳过(不需要验证的) continue; } checker = this.types[type]; 获取验证规则的验证类方法 if(!checker) { 验证规则类不存在 直接抛出异常 throw { name: "ValidationError",message: "No handler to validate type " + type } } resultOk = checker.validate(data[i]); if(!resultOk) { 验证不通过 this.messages.push(checker.instructions); } } } .hasErrors(); },hasErrors: () { this.messages.length !== 0; } }
使用方式如下:
var data = { firstName: '''' } validator.config = { firstName: 'isNonEmpty''isAlphaNum' } validator.validate(data); if(validator.hasErrors()) { console.log(validator.messages.join("\n")); } 结果: // 传入的值不能为空 传入的值只能是数字或者字母,不能是特殊字符
其它策略模式示例
jquery中使用的animate方法
$( div ).animate( {"left: 200px"},1000,'linear' ); 匀速运动 $( div ).animate( {"left: 200px"},'cubic' ); 三次方的缓动
这 2 句代码都是让 div 在 1000ms 内往右移动 200 个像素. linear(匀速) 和 cubic(三次方缓动) 就是一种策略模式的封装.
计算奖金
比如公司的年终奖是根据员工的工资和绩效来考核的,绩效为A的人,年终奖为工资的4倍,绩效为B的人,年终奖为工资的3倍,绩效为C的人,年终奖为工资的2倍;现在我们使用一般的编码方式会如下这样编写代码:
var calculateBouns = (salary,level) { if(level === 'A') { return salary * 4; } if(level === 'B'return salary * 3if(level === 'C'return salary * 2; } }; 调用如下: console.log(calculateBouns(4000,'A')); 16000 console.log(calculateBouns(2500,'B')); 7500
缺点:函数含有很多if语句,缺乏弹性,算法复用性差,如果其它地方有类似的算法,但是规则不一样,这些代码不能通用。
使用策略模式代码如下:
代码如下: var obj = { "A": (salary) { ; },"B" : ; } }; var calculateBouns =(level,salary) { return obj[level](salary); }; console.log(calculateBouns('A',10000)); 40000
策略模式不仅仅只封装算法,我们还可以对用来封装一系列的业务规则,只要这些业务规则目标一致,我们就可以使用策略模式来封装它们。
表单校验
比如常见的就是注册页面,需要对用户名,密码,手机号等进行规则校验,验证规则如下:
- 用户名不能为空;
- 密码不能小于6位;
- 手机号码符合手机正则规则
HTML代码如下:
<form action="" id="registerForm" method="post" onsubmit="return submitValidate()"> p> label>请输入用户名:</input type="text" name="userName" /> >请输入密码:="password" >请输入手机号码:="phoneNumber" divbutton ="submit">提交button> form>
submitValidate验证方法如下:
submitValidate() { var registerForm = document.getElementById("registerForm"); if(registerForm.userName.value === '') { alert('用户名不能为空'); false; } else if(registerForm.password.value.length < 6) { alert("密码的长度不能小于6位"); if(!/(^1[0-9]{10}$)/.test(registerForm.phoneNumber.value)) { alert("手机号码格式不正确"; }
缺点:
- submitValidate函数中的if else-if代码会根据验证项而逐渐变大;
- submitValidate函数缺乏弹性,如果添加新的校验规则是添加else-if语句,但是如果是修改原来的验证规则,那么就需要改函数内的代码,违反开放-封闭原则;
- 算法的复用性差,如果其它页面也需要用类似的校验,那么这个方法就不能共用了,可以又是复制代码。
下面我们使用策略模式来重构上面的代码。
第一步,封装策略对象,也即是验证的不同算法,代码如下:
var strategys = { isNotEmpty: if(value === '') { errorMsg; } },1)"> 限制最小长度 minLength: if(value.length < length) { 手机号格式 mobileFormat: .test(value)) { errorMsg; } } }
第二步,实现Validator类,Validator类在这里作为Context,负责接收用户的请求并委托给strategy 对象。
通俗的话就是添加表单中需要验证的一些规则以及获取验证结果(是否验证通过),如下代码:
Validator() { this.cache = []; 保存效验规则 } Validator.prototype = { constructor: Validator,add: (dom,rule,1)">var str = rule.split(":"); minLength:6的场景 var fn = () { var strategyType = str.shift(); 删除str数组中的第一个元素并返回,即是获取校验规则的函数名 str.unshift(dom.value); 往数组str的第一位插入value值 str.push(errorMsg); strategys[strategyType].apply(dom,str); } .cache.push(fn); },start: for(var i = 0,fn; fn = this.cache[i++];) { var msg = fn(); (msg) { msg; } } } }
调用方式:
var validator = new Validator(); validator.add(registerForm.userName,'isNotEmpty','用户名不能为空'); validator.add(registerForm.password,'minLength:6','密码的长度不能小于6位'); validator.add(registerForm.phoneNumber,'mobileFormat','手机号码格式不正确'var resultMsg = validator.start(); (resultMsg) { alert(resultMsg); ; }
完整的JS代码:
errorMsg; } } } msg; } } } } ; }
以上代码我们只实现了给一个dom元素绑定一条验证规则,那如果需要绑定多条验证规则呢?
比如上面的代码我们只能效验输入框是否为空,validator.add(registerForm.userName,'isNotEmpty','用户名不能为空');但是如果我们既要效验输入框是否为空,还要效验输入框的长度不要小于10位的话,那么我们期望需要像如下传递参数:
validator.add(registerForm.userName,[{strategy:'isNotEmpty',errorMsg:'用户名不能为空'},{strategy: 'minLength:10',errorMsg:'用户名长度不能小于10位'}])
var self = ; var rule = rules[i]; ((rule){ var str = rule.strategy.split(":"); minLength:6的场景 () { 删除str数组中的第一个元素并返回,即是获取校验规则的函数名 str.unshift(dom.value); str.push(rule.errorMsg); msg; } } } }
调用方式改变一下:
'用户名不能为空'
},{
strategy: 'minLength:10'用户名长度不能小于10位'
}]);
validator.add(registerForm.password,[{
strategy: 'minLength:6'
}]);
validator.add(registerForm.phoneNumber,[{
strategy: 'mobileFormat'
}]);
(resultMsg) {
alert(resultMsg);
;
}
完整的代码如下:
if (value === ''if (value.length <if (!/(^1[0-9]{10}$)/for ((rule) { (msg) { msg; } } } } ; }
当然我们也可以把验证各种类型的算法放到构造函数Validator原型上,这儿就不处理了。
关于表单校验的文章可参考:
总结
策略模式优点: