策略模式
# 策略模式
# 什么是策略模式
策略模式是一种行为设计模式,定义一系列算法,将每一个算法封装起来,并让它们可以相互替换;
策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy);
# 使用策略模式计算绩效奖金
很多公司的年终跟绩效挂钩:绩效为S的是2倍工资、绩效为A的为1.5倍工资、绩效为B的为1.2倍工资;
假设我们的财务需要一段代码方便计算年终奖;
# 最初的代码
我们可以通过编写一个名为calculateBonus的函数来计算每一个人的奖金数额;
很显然calculateBonus函数要正常工作责需要接受两个参数:基本工资和和绩效考核等级;
代码如下:
const calculateBonus = function(level, salary) {
if (level === 'S') {
return salary * 2;
}
if (level === 'A') {
return salary * 1.5;
}
if (level === 'B') {
return salary * 1.2;
}
}
calculateBonus('S', 1000);
// 输出 2000
calculateBonus('A', 1000);
// 输出 1500
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
可以发现,这段代码很简单,但是存在的显而易见的缺点
calculateBonus函数比较庞大,包含了很多if-else语句,这些语句需要覆盖所有的逻辑分支;calculateBonus函数缺乏弹性,如果增加了一种新的绩效等级C,或则想把绩效为S的奖金系数改为2.5,那么我们必须深入calculateBonus函数内部的实现,这是违反开放-封闭原则的;- 算法的复用逻辑性差,如果在程序的其他地方需要重用这些计算逻辑,我们只能
ctrl+c和ctrl+v;
开放-封闭原则: 对拓展开放、对修改关闭,用最少的代码,甚至不用代码修改原来的逻辑,只要添加新的扩展逻辑代码;
因此我们需要重构一下代码;
# 一轮修改后代码
const performanceS = function (salary) {
return salary * 2;
}
const performanceA = function (salary) {
return salary * 1.5;
}
const performanceB = function (salary) {
return salary * 1.2;
}
const calculateBonus = function (level salary) {
if (level === 'S') {
return performanceS(salary);
}
if (level === 'A') {
return performanceA(salary);
}
if (level === 'B') {
return performanceB(salary);
}
}
calculateBonus('S', 1000);
// 输出 2000
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
目前我们的程序得到了一定的改善,但是这种改善非常有限,我们依然没有解决最重要的问题:calculateBonus函数可能越来越庞大,而且在系统变化的时候缺乏弹性;
其实使用这些 if-else 的目的就是为了对应状态和计算方式。

# 使用策略模式重构代码
因为该文章写的是策略模式,显而易见,我们将要用策略模式对上述代码进行重构;
将不变的和变化的部分隔开是每一个设计模式的主题,策略模式也不例外; 策略模式的目的就是将算法的使用和算法的实现分离开来;
在这里例子里:算法的使用方式是不变的,而算法的实现是变化的;
一个基于策略模式的程序,必须有两部分组成: 第一部分:是一组策略类,策略类中封装了具体的算法过程 第二部分:环境类
Content,它可以接受客户请求,然后把请求委托给策略类;
模拟面向对象例子
策略对象:
const performanceS = function () {}
performanceS.prototype.calculate = function (salary) {
return salary * 2;
}
const performanceA = function () {}
performanceA.prototype.calculate = function (salary) {
return salary * 1.5;
}
const performanceB = function () {}
performanceB.prototype.calculate = function (salary) {
return salary * 1.2;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
下面定义Bonus:
const Bonus = function (){
this.salary = null; // 原始工资
this.stringify = null; // 绩效等级对应的策略对象
}
Bonus.prototype.setSalary = function (salary) {
this.salary = salary; // 设置原始工资
}
Bonus.prototype.setStringify = function (stringify) {
this.stringify = stringify; // 设置员工绩效等级对应的策略对象
}
Bonus.prototype.getBonus = function () {
return this.stringify.calculate(this.salary); // 把计算奖金的操作委托给策略对象
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
使用:
const bonus = new Bonus();
bonus.setSalary(1000);
bonus.setStringify(new performanceS); // 设置策略对象
console.log(bonus.getBonus()); // 输出 2000
bonus.setStringify(new performanceA); // 设置策略对象
console.log(bonus.getBonus()); // 输出 1500
2
3
4
5
6
7
8
9
上述代码,看着比较繁琐对吧,因为上面代码是基于传统面向对象语言的模仿;
实际在javascript语言中,函数也是对象,所以我们有更简单和直接的方法是把strategy直接定义为函数;
const strategies = {
'S': function(salary) {
return salary * 2;
},
'A': function(salary) {
return salary * 1.5;
},
'B': function(salary) {
return salary * 1.2;
},
};
2
3
4
5
6
7
8
9
10
11
同样,Context也没有必要用到Bonus类来表示,我们依然用calculateBonus函数充当Context来接受用户请求; 经过改造,代码结构变得更加简洁:
const calculateBonus = function(level, salary) {
return strategies[level](salary);
}
console.log(calculateBonus('S', 1000)); // 输出2000
2
3
4
5
es6类实现
//接下来定义奖金类Bonus:
class Bonus {
constructor() {
this.salary = null; // 原始工资
this.strategy = null; // 绩效等级对应的策略对象
}
setSalary(salary) {
this.salary = salary; // 设置员工的原始工资
}
setStrategy(strategy) {
this.strategy = strategy; // 设置员工绩效等级对应的策略对象
}
getBonus() { // 取得奖金数额
return this.strategy.calculate(this.salary); // 把计算奖金的操作委托给对应的策略对象
}
}
const bonus = new Bonus();
bonus.setSalary(1000);
bonus.setStrategy(new performanceS()); // 设置策略对象
console.log(bonus.getBonus()); // 输出:2000
bonus.setStrategy(new performanceA()); // 设置策略对象
console.log(bonus.getBonus()); // 输出:1500
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 总结
看到这里,我们对策略模式有了大致了解,下面我们总结一下,
# 流程图比较
之前的代码逻辑

优化后的代码逻辑

以上的优化策略就是使用了设计模式之策略模式,在实际的项目开发过程中还是比较实用。
# 优缺点
优点
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
- 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展。
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
- 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
缺点
- 增加许多策略类或者策略对象,但实际上这比把它们负责的 逻辑堆砌在 Context 中要好。
- 要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点, 这样才能选择一个合适的 strategy。
# 使用场景
在什么情况下可以考虑使用策略模式呢?如果函数具有以下特征:
- 判断条件很多
- 各个判断条件下的代码相互独立
然后可以将每个判断条件下的代码封装成一个独立的函数,然后建立判断条件和具体策略的映射关系。
# 拓展
# 多态在策略模式中的体现
通过使用策略模式的重构代码,我们消除了原程序中大片的条件分支语句;
所有跟计算逻辑不相关的不放在Context中,而是分布在各个策略对象中;
Context并没有计算能力,而是把这个职责委托给某个策略对象;
每个策略对象负责的算法已被各个自封在对象内部;
当我们对这些策略对象发出计算奖金的请求时,他们会返回各自不同的结果;
这正是多态的体现,也是他们可以相互替换的目的;
替换Content中当前保存的策略对象,便能执行不同的算法来得到我们想要的结果;
# 小事例(出自《JavaScript设计模式与开发实践》)
# 缓动动画
目标:编写一个动画类和一些缓动算法,让小球以各种各样的缓动效果在页面中运动
分析: 首先缓动算法的职责是实现小球如何运动 然后动画类(即context)的职责是负责:
- 初始化动画对象
在运动开始之前,需要提前记录一些有用的信息,至少包括以下信息:
- 动画开始时的准确时间点;
- 动画开始时,小球所在的原始位置;
- 小球移动的目标位置;
- 小球运动持续的时间。
- 计算小球某时刻的位置
- 更新小球的位置