这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战
定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
使用策略模式计算奖金
以计算年终奖为例,例如:绩效为S的人年终奖4倍工资,绩效为A的人年终奖3倍工资,绩效为B的人年终奖2倍,写一段代码来实现计算员工的年终奖。
1 最初的代码实现
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) // 输出:40000
calculateBonus( 'S', 6000 ); // 输出:24000
可以发现,这段代码非常简单,但是存在很多显而易见的缺点。
calculateBonus包含很多if-else语句,需要覆盖所有的逻辑分支。calculateBonus缺乏弹性,如果增加了绩效C,或者调整了奖金系数,都必须重新深入calculateBonus函数的内部去修改,这是违反开放-封闭原则。- 算法的复用性查,如果其他地方需要用,只有复制粘贴。
2 使用组合函数重构代码
将各种算法封装到一个个小函数里面,这些小函数有良好的命名,可以一目了然的知道对应哪种算法,它们也可以被复用。
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)
}
if (performanceLevel === 'A') {
return performanceA(salary)
}
if (performanceLevel === 'B') {
return performanceB(salary)
}
}
calculateBonus('A', 10000) // 输出:30000
3 使用策略模式重构代码
策略模式指定义一系列算法,把它们一个个封装起来。将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式的目的就是将算法的使用与算法的实现分离开来。
一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二部分是环境类 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(strategy) {
this.strategy = strategy // 设置员工绩效工资
}
Bonus.prototype.getBouns = function() {
return this.strategy.calculate(this.salary) // 计算奖金的操作委托给对应的策略对象
}
策略模式的思想:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。在 JavaScript 中表现为他们具有相同的目标和意图
接下来,来完成剩下的代码。先创建一个 bonus 对象,并且给 bonus 对象设置一些原始的数据。将计算奖金的策略对象也传入 bonus 对象内部保存。当调用 bonus.getBonus() 来计算奖金时,bonus 对象本身没有能力计算,而是把请求委托给了之前保存好的策略对象。
var bonus = new Bonus()
bonus.setSalary(10000)
bonus.setStrategy(new performanceS()) // 设置策略对象
console.log(bonus.getBonus()) // 输出:40000
bonus.setStrategy(new performanceA()) // 设置策略对象
console.log(bonus.getBonus()) // 输出:30000
JavaScript 版本的策略模式
在 JavaScript 中,函数也是对象,因此更简单和直接的做法是把 strategy 直接定义为函数:
var strategies = {
S: function(salary) {
return salary * 4
},
A: function(salary) {
return salary * 3
},
B: function(salary) {
return salary * 2
}
}
同样,Context 也没必要必须用 Bonus 类来表示,定义 calculateBonus 函数。
var calculateBonus = function(level, salary) {
return strategies[level](salary)
}
console.log(calculateBonus('S', 20000)) // 输出:80000
console.log(calculateBonus('A', 10000)) // 输出:30000
使用策略模式实现缓动动画
我们的目的的实现一个小球按照不同的算法进行运动。
实现动画效果的原理
使用 JavaScript 实现动画效果的原理跟动画片的制作一样,动画片是把一些差距不大的原画以较快的帧数播放,来达到视觉上的动画效果。在 JavaScript 中,可以通过连续改编元素的某个 css 属性,比如 left、top、background-position 来实现动画效果
思路和一些准备工作
在运动开始前,需要记录一些信息,至少包括:
- 动画开始时,小球所在的原始位置
- 小球移动的目标位置
- 动画开始时的准确时间点
- 小球运动持续的时间
随后,使用
setInterval创建一个定时器,每隔19ms循环一次,在定时器的每一帧里,将动画已消耗的时间、小球原始位置、小球目标位置以及动画持续总时间传入缓动算法,计算出小球当前应该所在的位置,然后更新div对应的css属性。如此小球便可顺利的运动起来。
让小球运动起来
缓动算法接受 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) * 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;
},
};
下面的代码思想来自 jQuery 库,先在页面放置一个 div:
<body>
<div style="position: absolute; background: blue" id="div">我是 div</div>
</body>
接下来定义 Animate 类,Animate 的构造函数接受一个参数:即将运动起来的 dom 节点。
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 启动这个动画,需要记录一些信息供以后计算小球位置时使用,此方法还负责启动定时器。
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.start 方法接受一下四个参数:
propertyName: 要改变的css属性名,比如left、top,分别表示左右移动和上下移动。endPos:小球运动的目标位置。duration:动画持续时间。easing:缓动算法。Animate.prototype.step代表小球运动的每一帧要做的事情。
Animate.prototype.step = function () {
var t = +new Date(); // 取得当前时间
if (t >= this.startTime + this.duration) {
// 动画结束时修正小球的位置
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 更新小球 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");
更广义的“算法”
策略模式指的是定义一系列的算法,并且把他们封装起来。
从定义上看,策略模式就是用来封装算法的。但如果把策略模式仅仅用来封装算法,有点大材小用。实际开发中,通常会把算法的含义扩散开来,使策略模式也可以用来封装一系列的“业务规则”。只要业务规则指向的目标一致,并且可以被替换使用,我们就可以使用策略模式来封装。
策略模式的优缺点
优点:
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
- 策略模式提供了对开放-封闭原则的完美支持,将算法封装在独立的
strategy中,使得它们易于切换,易于理解,易于扩展。 - 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
- 在策略模式中利用组合和委托来让
Context拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
缺点:
- 使用策略模式会在程序中增加许多策略类或者策略对象
- 使用策略模式必须了解所有的
strategy,了解各个strategy之间的不同点,才能选择一个合适的。
最后说一句
如果这篇文章对您有所帮助,或者有所启发的话,帮忙点赞关注一下,您的支持是我坚持写作最大的动力,多谢支持。
同系列文章
- JavaScript 设计模式之单例模式
- JavaScript 设计模式之策略模式
- JavaScript 设计模式之代理模式
- JavaScript 设计模式之迭代器模式
- JavaScript 设计模式之发布-订阅模式
- JavaScript 设计模式之命令模式
- JavaScript 设计模式之组合模式
- JavaScript 设计模式之模板方法模式
- JavaScript 设计模式之享元模式
- JavaScript 设计模式之职责链模式
- JavaScript 设计模式之中介者模式
- JavaScript 设计模式之装饰者模式
- JavaScript 设计模式之状态模式
- JavaScript 设计模式之适配器模式