简介
在程序设计中,我们经常遇到类似的情况,要实现某一个功能有多种方案可以选择,比如一个压缩文件的程序,既可以选择zip算法,也可以使用gzip算法。策略模式的定义是:定义一系列的算法,把它们一个一个封装起来,并且使它们可以相互替换。
使用策略模式计算奖金
需求为绩效S的人4倍奖金,绩效为A的人3倍奖金,绩效为B的人年终奖是2倍工资。
var caculateBonus = function(performanceLevel, salary){
if(performanceLevel === 'S'){
return salary * 4;
}
if(performanceLevel === 'A'){
return salary * 3;
}
if(performanceLevel === 'B'){
return salary * 2;
}
}
这段代码有一些缺点,如果我们想改变绩效的奖金系数,我们必须修改calculateBonus函数的内部实现,这是违反开放-封闭原则的。算法的复用性很差,如果在程序的其他地方需要重用这些计算奖金的方法,就只有复制和粘贴。
使用组合函数重构代码
我们把各种算法封装到一个个小函数里面,这些小函数有着良好的命名,可以一目了然知道它对应者哪种算法
var performanceS = function(salary){
return salary * 4;
};
var performanceA = function(salary){
return salary * 3;
};
var performanceB = function(salary){
return salary * 2;
}
var calculateBonus = function(performanceLevel, salary){
if(performanceLevel === 'S'){
return performanceS(salary);
}
//...
}
这样依然没有解决我们的问题
使用策略模式重构代码
一个基于策略模式的程序至少有两部分组成,第一个部分是策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类Context,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;
};
接下来定义奖金类Bonus
var Bonus = function(){
this.salary = null;
this.strategy = null; //绩效等级对应的策略对象
}
Bonus.prototype.setSalary = function(salary){
this.salary = salary;
}
Bonus.prototype.setStrategy = function(salary){
this.strategy = strategy;
}
Bonus.prototype.getBonus = function(){
return this.strategy.calculate(this.salary);
}
接下来
var bonus = new Bonus();
bonus.setSalary(10000);
bonus.setStrategy(new performanceS());//设置策略对象
console.log(bonus.getBonus());
bonus.setStrategy();
js版本的策略模式
我们让strategy对象从各个策略类中创建而来,这时模拟一些传统的面向对象语言的实现,实际上在js中,函数也是对象,所以更简单的做法是把strategy直接定义成函数
//[ˈstrætədʒi]策略
var strategies = function(){
'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));
console.log(calculateBonus('A', 10000));
使用策略模式实现缓动动画
这些方法都接受4个参数,这4个参数的含义分别是动画已消耗的时间,小球原始位置,小球目标位置和动画持续的时间,返回的值则是动画元素应该处在的当前位置
var tween = {
linear: function(t, b, c, d){
return c * t / d + b;
},
easeIn: function(t, b, c, d){
return c * t / d + b;
} },
strongEaseIn: function(t, b, c, d){
return c * t / d + b;
}
}
现在进入代码实现阶段,首先在页面放置一个div
<body>
<div style="position:absolute;background:blue" id="div"></div>
</body>
接下来定义Animate类,Animate的构造函数接受一个参数:即将运动起来的DOM节点
var Animate = function(dom){
this.dom = dom;
this.startTime = 0;
this.startPos = 0;
this.endPos = 0;
//dom节点需要被改变的css属性名
this.propertyName = null;
//缓动算法
this.easing = null;
//动画持续时间
this.duration = null;
}
Animate.prototype.start = function(propertyName, endPos, duration, easing){
this.startTime = new Date.getTime();
//getBoundingClientRect
this.startPos = this.dom.getBoundingClientRect()[propertyName]; //dom节点初始位置
this.propertyName = propertyName;
this.endPos = endPos;
this.duration = duration;
//缓动算法
this.easing = tween[easing];
var self = this;
var timeId = setInterval(function(){
//如果动画已结束,则清除定时器
//这里直接进行了动画调用
if(self.step() === false){
clearInterval(timeId);
}
}, 19);
}
Animate.prototype.step = function(){
var t = new Date().getTime();
if(t >= this.startTime + this.duration){
//更新小球的css属性值
this.update(this.endPos);
return false;
}
var pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration);
//pos为小球当前位置
this.update(pos); //更新小球的css属性值
}
最后是负责更新小球属性值的Animate.prototype.update方法
Animate.prototype.update = function(pos){
this.dom.style[this.propertyName] = pos + 'px';
}
//测试代码
var div = document.getElementById('div');
var animate = new Animate(div);
animate.start('left', 1500, 500, 'strongEaseIn');
我们最终通过对象来定义算法,并且通过属性传入调用对象中,达到了方便替换各种算法的效果。
表单校验
我们首先要将表单的校验逻辑都封装成策略对象
var strategies = {
isNonEmpty: function(value, errorMsg){
if(value === ''){
return errorMsg;
}
},
minLength: function(value, length, errorMsg){
if(value.length < length){
return errorMsg;
}
},
isMobile: function(value){
if(!/(^1[3|5|8][0-9]{9}$)/.test(value)){
return errorMsg;
}
}
}
然后实现一个validataFunc类
var Validator = function(){
//保存校验规则
this.cache = [];
};
Validator.prototype.add = function(dom, rule, errorMsg){
//把strategy和参数分开
var arr = rule.split(':');
this.cache.push(function(){
//statefy: minLength
var strategy = arr.shift();
//[dom.value, 10]
arr.unshift(dom.value);
//[dom.value, 10, errorMsg]
arr.push(errorMsg);
//return minLength.apply(dom, [dom.value, 10, errorMsg])
return strategies[strategy].apply(dom, arr);
})
};
Validator.prototype.start = function(){
for(var i = 0; i < this.cache.length; i++;){
var msg = this.cache[i];
//如果有确切的返回值,说明校验没有通过
if(msg){
return msg;
}
}
}
这样重构之后,我们在修改某个校验规则的时候,只需要编写或者改写少量的代码
validator.add(registerForm.userName, 'minLength: 10', '用户名长度不能小于10位');
策略模式也有一些缺点,使用策略模式会在程序中增加许多策略对象