策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换(相互替换是相对于静态类型语言而言的。因为静态类型语言中有类型检查机制,所以各个策略类需要实现同样的接口。当它们的真正类型被隐藏在接口后面时,它们才能被相互替换。 而在 JavaScript 这种类型模糊的语言中没有这种困扰,任何对象都可以被替换使用。因此,JavaScript表现为它们具有相同的目标和意图)。而他的目的就是将算法的使用与算法的实现分离开来。
而当我们写代码时,需要写一大堆的if-else语句时,就要考虑是否需要用策略模式了。通过策略模式,我们可以把策略从业务代码中抽离出来,未来扩展的话无需深入到业务代码修改,只需要新增需要的策略,不会使得业务代码变得越来越臃肿,也可以更好的服用代码。
策略模式分为两部分,第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类 Context,Context 接受客户的请求,随后把请求委托给某一个策略类。
案例1:去掉if-else
/** 一大堆的if-else **/
//level 评级 basicBonus 基础奖金
const computeBonus(level, basicBonus) = () => {
if(level === 'A') {
return basicBonus * 1;
} else if(level === 'B') {
return basicBonus * 2;
} else if(level === 'C') {
return basicBonus * 3;
} else if(level === 'D') {
return basicBonus * 4;
}
}
computeBonus('A', 1000);//1000
/** 策略模式改写 **/
//定义策略类
const strategies = {
'A': function(basicBonus) {
return basicBonus * 1;
},
'B': function(basicBonus) {
return basicBonus * 2;
},
'C': function(basicBonus) {
return basicBonus * 3;
},
'D': function(basicBonus) {
return basicBonus * 4;
},
}
//使用环境类
const computeBonus = (level, basicBonus) {
return strategies[level](basicBonus);
}
computeBouns('A', 1000);//1000
案例2:表单验证
案例3:写动画
//(策略类)定义不同动作计算方法,分离这些方法出来,而不是用if-else一个个判断
//参数分别为动画已消耗的时间、小球原始位置、小球目标位置、动画持续的总时间
var tween = {
linear: function( t, b, c, d ){
return c*t/d + b;
},
easeIn: function( t, b, c, d ){
return c * ( t /= d ) * t + b;
},
strongEaseIn: function(t, b, c, d){
return c * ( t /= d ) * t * t * t * t + b;
},
strongEaseOut: function(t, b, c, d){
return c * ( ( t = t / d - 1) * t * t * t * t + 1 ) + b;
},
sineaseIn: function( t, b, c, d ){
return c * ( t /= d) * t * t + b;
},
sineaseOut: function(t,b,c,d){
return c * ( ( t = t / d - 1) * t * t + 1 ) + b;
}
};
var Animate = function( dom ){
this.dom = dom; // 进行运动的 dom 节点
this.startTime = 0; // 动画开始时间
this.startPos = 0; // 动画开始时,dom 节点的位置,即 dom 的初始位置
this.endPos = 0; // 动画结束时,dom 节点的位置,即 dom 的目标位置
this.propertyName = null; // dom 节点需要被改变的 css 属性名
this.easing = null; // 缓动算法
this.duration = null; // 动画持续时间
};
//(环境类)动画动作
Animate.prototype.start = function( propertyName, endPos, duration, easing ){
this.startTime = +new Date; // 动画启动时间
this.startPos = this.dom.getBoundingClientRect()[ propertyName ]; // dom 节点初始位置
this.propertyName = propertyName; // dom 节点需要被改变的 CSS 属性名
this.endPos = endPos; // dom 节点目标位置
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; // 取得当前时间
if ( t >= this.startTime + this.duration ){ // (1)
this.update( this.endPos ); // 更新小球的 CSS 属性值
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 = function( pos ){
this.dom.style[ this.propertyName ] = pos + 'px';
};
//创建节点并且使用动画
var div = document.getElementById( 'div' );
var animate = new Animate( div );
animate.start( 'left', 500, 1000, 'strongEaseOut' );