条条大路通罗马
策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
初探策略模式
问题:绩效为 S 的人年终奖有 4 倍工资,绩效为 A 的人年终奖有 3 倍工资,而绩效为 B 的人年终奖是 2 倍工资。
代码实现:
// 定义一个方法
var calculateBonus = function(performanceLevel,salary){
if(performanceLevel==='S'){
return salary*4
}
if(performanceLevel==='A'){
return salary*3
}
if(performanceLevel==='B'){
return salary*2
}
}
// 张三的工资
var zhangSanWages = calculateBonus('S',200000)
console.log(zhangSanWages,'zhangSanWages');
问题的深入:
- calculateBonus 函数比较庞大,包含了很多 if-else 语句,这些语句需要覆盖所有的逻辑 分支。
- calculateBonus 函数缺乏弹性,如果增加了一种新的绩效等级 C,或者想把绩效 S 的奖金
系数改为 5,那我们必须深入 calculateBonus 函数的内部实现,这是违反开放封闭原则的。
组合函数重构代码
var performanceLevelS=function(salary){
return salary*4
}
var performanceLevelA=function(salary){
return salary*3
}
var performanceLevelB=function(salary){
return salary*2
}
var calculateBonus = function(performanceLevel,salary){
if(performanceLevel==='S'){
return performanceLevelS(salary)
}
if(performanceLevel==='A'){
return performanceLevelS(salary)
}
if(performanceLevel==='B'){
return performanceLevelS(salary)
}
}
var zhangSanWages = calculateBonus('B',100000)
console.log(zhangSanWages,'zhangSanWages');
使用策略模式重构
一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类 Context,Context 接受客户的请求,随后把请求委托给某一个策略类。
策略类的实现
var performanceLevelS = function () {}
performanceLevelS.prototype.calculate = function (salary) {
return salary * 4
}
var performanceLevelA = function () {}
performanceLevelA.prototype.calculate = function (salary) {
return salary * 3
}
var performanceLevelB = function () {}
performanceLevelB.prototype.calculate = function (salary) {
return salary * 2
}
var performanceLevelC = function () {}
performanceLevelC.prototype.calculate = function (salary) {
return salary * 1
}
环境类的实现 定义奖金类 Bonus
// 环境类的实现 定义奖金类 Bonus
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 () {
// 查看this
console.log(this,this.strategy);
return this.strategy.calculate(this.salary); // 把计算奖金的操作委托给对应的策略对象
}
var bonus = new Bonus();
全部代码
// 策略类的实现
var performanceLevelS = function () {}
performanceLevelS.prototype.calculate = function (salary) {
return salary * 4
}
var performanceLevelA = function () {}
performanceLevelA.prototype.calculate = function (salary) {
return salary * 3
}
var performanceLevelB = function () {}
performanceLevelB.prototype.calculate = function (salary) {
return salary * 2
}
var performanceLevelC = function () {}
performanceLevelC.prototype.calculate = function (salary) {
return salary * 1
}
// 环境类的实现 定义奖金类 Bonus
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 () {
// 查看this
console.log(this,this.strategy);
return this.strategy.calculate(this.salary); // 把计算奖金的操作委托给对应的策略对象
}
var bonus = new Bonus();
bonus.setSalary(10000);
bonus.setStrategy(new performanceLevelS()); // 设置策略对象
console.log(bonus.getBonus()); // 输出:40000
js中的策略模式
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 ),'输出绩效' );
多态在策略模式中的体现
通过使用策略模式重构代码,我们消除了原程序中大片的条件分支语句。所有跟计算奖金有关的逻辑不再放在 Context 中,而是分布在各个策略对象中。Context 并没有计算奖金的能力,而是把这个职责委托给了某个策略对象。每个策略对象负责的算法已被各自封装在对象内部。当我们对这些策略对象发出“计算奖金”的请求时,它们会返回各自不同的计算结果,这正是对象多态性的体现,也是“它们可以相互替换”的目的。替换 Context 中当前保存的策略对象,便能执行不同的算法来得到我们想要的结果。
动画中的策略模式
如果让一些不太了解前端开发的程序员来投票,选出他们眼中 JavaScript 语言在 Web 开发中
的两大用途,我想结果很有可能是这样的:
编写一些让 div 飞来飞去的动画
验证表单
思路和一些准备工作
动画开始时,小球所在的原始位置;
小球移动的目标位置;
动画开始时的准确时间点;
小球运动持续的时间。
缓动算法
var teew = {
// 动画已消耗的时间、小球原始位置、小球目标位置、动画持续的总时间,返回的值则是动画元素应该处在的当前位置。
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;
}
}
让我们的小球动起来
Animate类
var Animate = function (dom) {
this.dom = dom //进行运动的节点
this.startTime = 0
this.startPos = 0
this.endStart = 0
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
this.endPos = endPos
this.duration = duration
this.easing = teew[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 属性值
}
// 负责更新小球 CSS 属性值
Animate.prototype.update = function (pos) {
this.dom.style[this.propertyName] = pos + 'px';
}
全部代码
<div style="position:absolute;background:blue" id="div">我是 div</div>
<script>
/**
* 实现思想
* 动画开始小球的位置
* 小球移动的目标位置
* 动画开始的时间点
* 小球移动的持续时间
*/
var teew = {
// 动画已消耗的时间、小球原始位置、小球目标位置、动画持续的总时间,返回的值则是动画元素应该处在的当前位置。
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 //进行运动的节点
this.startTime = 0
this.startPos = 0
this.endStart = 0
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
this.endPos = endPos
this.duration = duration
this.easing = teew[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 属性值
}
// 负责更新小球 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('right', 500, 1000, 'linear');
</script>