js 策略模式
例如,很多公司的年终奖是根据员工的工资基数和年底绩效情况来发放的.例如,绩效为s的人年终奖有4倍工资,
绩效为A的人年终奖有3倍工资,而绩效为B的人年终奖是2倍工资.
最初思路
编写一个calculateBonus的函数来计算每个人的奖金数额.
这个函数,接受两个参数:员工的工资数额和他的绩效考核等级.
var calculateBonus = function(performanceLevel,salary){
if(performanceLevel === 'S'){
return salary * 4;
}
if(performanceLevel === 'A'){
return salary * 3;
}
if(performanceLevel === 'B'){
return salary * 2;
}
}
calculateBonus('B',20000);
calculateBonus('S',10000);
缺点:
calculateBonus函数比较庞大,包含了很多的if-else语句,这些语句需要覆盖所有的逻辑分支
calculateBonus函数缺乏弹性,如果再增加一种新的绩效考核,我们必须修改函数内部实现.
这违反了开放-封闭原则
算法的复用性差,如果在程序的其它地方需要重用这些计算奖金的算法,我们的复制过去;
使用组合函数重构代码
一般最容易想到的办法就是使用组合函数来重构代码,我们把各种算法封装到一个个的小函数里面,
这些小函数有良好的命名,可以一目了然地知道它对应哪种算法,它们可以复用到程序的其它地方:
var performanceS = function(salary){
return salary * 4;
}
var performanceA = function(salary){
return salary * 3;
}
var performanceB = function(salary){
return salary * 2;
}
var calculateBons = function(performanceLevel,salary){
if(performanceLevel === 'S'){
return performanceS(salary)
}
if(performanceLevel === 'A'){
return performanceA(salary)
}
if(performanceLevel === 'B'){
return performanceB(salary)
}
}
console.log(calculateBons('A',10000));
目前,虽然程序上得到一定程度上的改善,但是这种改善风格有限,问题仍然存在:
calculateBons 函数有可能越来越大,而且在系统变化的时候缺乏弹性.
使用策略模式重构代码
策略模式指的是定义一系列的算法,把它们封装起来.目的就是将算法的使用与算法的实现分离出来
一个基于策略模式的程序至少由两部分组成.第一部分是一组策略类,封装了具体的算法,并负责具体的计算过程;
第二部分是环境类Context,Context接受用户的请求,随后把请求委托给某一个策略类.
var performanceS = function(){};
performanceS.prototype.calculate = function(salary){
return salary * 4;
}
var performanceA = function(){};
performanceA.prototype.calculate = function(salary){
return salary * 3;
}
var performanceB = function(){};
performanceB.prototype.calculate = function(salary){
return salary * 2;
}
var Bonus = function(){
this.salary = null;//原始工资
this.strategy = null;//绩效等级对应的策略对象
}
Bonus.prototype.setSalary = function(salary){
this.salary = salary;// 设置员工的原始工资
}
Bonus.prototype.setStrategy = function(strategy){
this.strategy = strategy;//设置员工绩效等级对应的策略对象;
}
Bonus.prototype.getBonus = function(){// 取得奖金数额
return this.strategy.calculate(this.salary);//把计算奖金的操作委托给对应的策略对象
}
var bouns = new Bonus();
bouns.setSalary(10000);
bouns.setStrategy(new performanceS());//设置策略对象
console.log(bouns.getBonus());
我们让strategy对象从各个策略类中创建而来,这是模拟一些传统面向对象实现.
实际上,在js中,函数也是对象,所以更简单和直接的做法是把strategy直接定义为函数;
var strategies = {
'S':function(salary){
return salary * 4;
},
'A':function(salary){
return salary * 3;
},
'B':function(salary){
return salary * 2;
}
};
/*
同样,Context也没有必要用Bonus类来表示,我们已然用calculateBonus函数充当Context来接受用户的请求
* */
var calculateBonus = function(level,salary){
return strategies[level](salary);
};
console.log(calculateBonus('S',20000));
接下来,我们用策略模式重构表单验证
var $btn = document.getElementsByTagName('button')[0],
$registerForm = document.getElementById('registerForm');
$btn.onclick = function(){
if($registerForm.userName.value === ''){
alert('用户名不能为空');
return false;
}
if($registerForm.password.value.length < 6){
alert('密码长度不能少于6位');
return false;
}
if(!/(^1[3|5|8][0-9]{9}$)/.test( $registerForm.phoneNumber.value ) ){
alert('手机号码格式不正确');
return false;
}
}
这是一种常见的代码编写方式,缺点如下"
$btn.onclick函数比较庞大,包含了很多的if-else语句,这些语句覆盖了所有的校验规则
$btn.onclick函数缺乏弹性,如果增加了一种新的校验规则,或者想把密码的长度校验从6改成8,
都必须深入函数内部去修改,这是违反了开放-封闭原则;
算法的复用性差,如果在程序中增加了另外一个表单,得复制过去;
用策略模式重构表单校验
很显然第一部要把这些校验逻辑都封装成策略对象:
var strategies = {
isNonEmpty:function(value,errorMsg){// 不为空
if(value === ''){
return errorMsg;
}
},
minLength:function(value,length,errorMsg){// 限制最小长度
if(value.length < length){
return errorMsg;
}
},
isMobile:function(value,errorMsg){// 手机号码格式
if(!/(^1[3|5|8][0-9]{9}$)/.test( value )){
return errorMsg;
}
}
}
接下来我们实现Validator类,Validator类在这里我们作为Context,负责接受用户的请求
并委托给strategy对象.在给出Validator类的代码前,有必要提前了解用户是如何向Validator类发送请求的,
有助于我们知道如何去编写Validator类的代码
var validataFunc = function(){
var validator = new Validator();//创建一个validator对象
/**添加一些校验规则**/
validator.add(document.getElementById('userName'),'isNonEmpty','用户名不能为空');
validator.add(document.getElementById('password'),'minLength:10','密码长度不能少于10位');
validator.add(document.getElementById('phoneNumber'),'isMobile','手机号码格式不正确');
var errorMsg = validator.start();//获得校验结果
return errorMsg;
};
$btn.onclick = function(){
var errorMsg = validataFunc();// 如何errorMsg有确切的返回值,说明未通过校验
if(errorMsg){
alert(errorMsg);
return false;//阻止表单提交
}
};
可以看到,我们先创建了一个validator对象,然后通过validator.add方法,往validator对象中添加一些校验规则,
validator.add方法接受3个参数,
'minLength:6'是一个以冒号隔开的字符串;
当往validator对象里添加完一系列的校验规则之后,会调用validator.start()方法来启动校验.
如果validator.start()返回一个确切的errorMsg字符串当作返回值,说明该校验没通过,
此时阻止表单提交
最后就是Validator类的实现
var Validator = function(){
this.cache = []; //保存校验规则
};
Validator.prototype.add = function( dom, rule, errorMsg ) {
var ary = rule.split(':'); //把strategy和参数分开
this.cache.push(function () { //把校验的步骤用空函数包装起来,并且放入cache
var strategy = ary.shift();// 用户挑选的strategy
ary.unshift(dom.value);//把input的value添加进参数列表
ary.push(errorMsg); //把errorMsg添加参数列表
return strategies[strategy].apply(dom, ary);
});
};
Validator.prototype.start = function(){
console.log(this.cache);
for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){
var msg = validatorFunc();// 开始校验,并取得校验后的返回信息
if ( msg ){// 如果有确切的返回值,说明校验没有通过;
return msg;
}
}
};