策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换,策略模式的目的就是将算法的使用与算法的实现分离开来。
首先我们来看一个情景:很多公司的年终奖是根据员工的绩效来进行发放,绩效为S的人,年终奖有4倍工资,绩效为A的人有3倍工资,绩效为B的人有2倍工资。我们怎么用代码来实现这个问题呢?
很简单粗暴的会想到:
var calculateBonus = function (performacceLevel, salary) {
if (performacceLevel === "S") {
return salary * 4;
}
if (performacceLevel === "A") {
return salary * 3;
}
if (performacceLevel === "B") {
return salary * 2;
}
};
这段的代码存在什么问题呢?
过多的if-else
,函数缺乏弹性,如果我们现在又增加一种绩效C
,那么我们该怎么办,我们只有手动去改我们的代码,这也就违反了我们所说的开闭原则。
下面我们利用策略模式来对这段代码进行重构:
一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个是环境类Context,Context接受客户的请求后,随后把请求委托给某一个策略类。再说明白点就是,Context把客户发起的请求,委托给了一个策略类new
出来的一个策略对象。
// 定义三种策略类
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 (stratrgy) {
this.strategy = stratrgy;
};
Bonus.prototype.getBonus = function () {
return this.strategy.calculate(this.salary);
};
var bonus = new Bonus();
bonus.setSalary(20000);
bonus.setStrategy(new PerformanceS());
console.log(bonus.getBonus()); // 80000
bonus.setStrategy(new PerformanceA());
console.log(bonus.getBonus()); // 60000
这种方式是仿造传统面向对象语言实现的,利用js的特性,函数就是对象,所以我们可以将策略类定义为一个对象,PerformanceS,PerformanceA,PerformanceB
设置为该对象的方法,就有如下代码:
var strategies = {
S: function (salary) {
return salary * 4;
},
A: function (salary) {
return salary * 3;
},
B: function (salary) {
return salary * 2;
}
};
var calculateBonus = function (level, salary) {
return strategies[level](salary);
};
console.log(calculateBonus("S", 20000)); // 80000
console.log(calculateBonus("B", 20000)); // 40000
下面来看策略模式的两个具体应用:
编写一个动画运动,其中tween
为策略类对象,其中的每一种方法,代表其每一种策略
// 策略类
var tween = {
linear: function (useTime, originPos, targetPos, allTime) {
return targetPos * useTime / allTime + originPos;
},
easeIn: function (useTime, originPos, targetPos, allTime) {
return targetPos * (useTime /= allTime) * useTime + originPos;
},
strongEaseIn: function (useTime, originPos, targetPos, allTime) {
return targetPos * (useTime /= allTime) * useTime * useTime * useTime * useTime + originPos;
},
strongEaseOut: function (useTime, originPos, targetPos, allTime) {
return targetPos * ((useTime = useTime / allTime - 1) * useTime * useTime * useTime * useTime + 1) + originPos;
},
sineaseIn: function (useTime, originPos, targetPos, allTime) {
return targetPos * (useTime /= allTime) * useTime * useTime + originPos;
},
};
// 运动类,easing可以采取不同的策略
var Animate = function (dom) {
this.dom = dom;
this.startTime = 0;
this.startPos = 0;
this.endPos = 0;
this.propertyName = null; // 节点需要被改变的css属性名
this.easing = null; // 运动算法
this.duration = null;
};
Animate.prototype.start = function (propertyName, targetPos, duration, easing) {
this.startTime = +(new Date());
this.startPos = this.dom.getBoundingClientRect()[propertyName];
this.endPos = targetPos;
this.duration = duration;
this.easing = tween[easing];
this.propertyName = propertyName;
var self = this,
timeId = setInterval(function () {
if (self.step() === false) {
clearInterval(timeId);
timeId = null;
}
}, 20);
};
Animate.prototype.step = function () {
var time = +(new Date()),
pos;
if (time >= this.startTime + this.duration) {
this.update(this.endPos);
return false;
}
pos = this.easing(time - this.startTime, this.startPos, this.endPos - this.startPos, this.duration);
this.update(pos);
};
Animate.prototype.update = function (pos) {
pos = pos | 0;
this.dom.style[this.propertyName] = pos + "px";
};
// 测试代码
var div = document.getElementById("div"),
animate = new Animate(div);
animate.start("left", 500, 1000, "easeIn");
// html代码
<div id="div" style="position: absolute;height:100px;width:100px;background:red;"></div>