定义:定义一些列的算法,把他们一个个封装起来,并使它们可以互相替换
引用书中写的很好的一段话来诠释策略模式吧,“俗话说条条大路通罗马,现实中也有很多种不同的途径到达同一个目的地,比如我们要去旅游,如果我们时间很赶,但是不考虑钱的问题,我们可能选择坐飞机,如果我们没有什么钱,那么我们可以坐大巴或者火车,如果再穷一点,可以选择骑行”。这段话就很好的诠释了什么是策略模式,就是对于一个问题,我们可以提供多种手段去解决,而且最终的结果都是期望的结果。
下面我们拿员工奖金计算算法为例
问题是这样的,员工到了年终都会进行绩效评级,最终根据所评的等级和自身薪资情况决定年终奖的多少,绩效评级分别有:S级,A级,B级,其中S级是拿基础薪资的4倍,A级是拿基础薪资的3倍,B级是拿基础薪资的2倍,根据以上情况,设计一个计算员工年终奖的算法模块
“最笨的方法”
function getBouns(performanceLevel, salary) {
if (performanceLevel === "S") {
return salary * 4
}
if (performanceLevel === "A") {
return salary * 3
}
if (performanceLevel === "B") {
return salary * 2
}
}
console.log(getBouns("A", 20000))
console.log(getBouns("S", 6000))
不难发现,代码十分简单,问题也十分暴露,getBouns函数十分庞大,存在了很多if-else的分支,且getBouns函数缺乏弹性,如果增加一个新的绩效等级,或者需要将S级的年终奖改为5倍基础薪资,那么就不得不去getBouns内部去修改源码,而且算法复用性极差 。
使用组合函数重构代码
let performanceS = function (salary) {
return salary * 4
}
let performanceA = function (salary) {
return salary * 3
}
let performanceB = function (salary) {
return salary * 2
}
function getBouns(performanceLevel, salary) {
if (performanceLevel === "S") {
return performanceS(salary)
}
if (performanceLevel === "A") {
return performanceA(salary)
}
if (performanceLevel === "B") {
return performanceB(salary)
}
}
console.log(getBouns("S" , 5000))
console.log(getBouns("B" , 2000))
相信大家看到这段代码会嗤之以鼻一下吧,这....也算改进?哈哈,我一开始看到这个重构代码也是很疑惑,但是确实有所提高,起码抽离出了计算逻辑,减少了getBouns的赘余,不过也就仅此而已
使用策略模式重构代码
在这个例子中,算法的使用方式是不变的,都是根据某个算法取得计算后的奖金数额,而算法的实现是变化各异的,每种绩效都对应着不同的算法规则。
一个基于策略模式的程序至少由两个部分组成,第一部分是一组策略类,策略类封装了具体的算法,并负责计算过程,第二部分则是环境类,环境类接受客户的请求后,将请求交付给策略类,执行对应的算法规则,在一下重构的过程中,我们同上篇“单例模式”文章一样,先模拟传统面向对象语言的写法,之后再实现JavaScript的版本
// 定义一组策略类
const performanceS = function () { }
performanceS.prototype.calculate = function (salary) {
return salary * 4
}
const performanceA = function () { }
performanceA.prototype.calculate = function (salary) {
return salary * 3
}
const performanceB = function () { }
performanceB.prototype.calculate = function (salary) {
return salary * 2
}
// 定义奖金类(环境类,此时的业务环境为计算奖金)
const 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 () {
if (!this.strategy) {
throw new Error("未设置响应的strategy属性")
}
return this.strategy.calculate(this.salary)
}
const bonus = new Bonus()
bonus.setSalary(10000)
bonus.setStrategy(new performanceS())
console.log(bonus.getBonus())
bonus.setSalary(2000)
bonus.setStrategy(new performanceA())
console.log(bonus.getBonus())
在实现JavaScript版本之前,仔细理解这段代码,很容易观察到,在传统面向对象语言中,为什么不将这些个策略类封装成一个对象呢,因为传统面向对象语言创建较为繁琐,还需要先创建一个构造类再来创建对象封装策略类,那么熟悉JavaScript的小伙伴就清楚了,在JavaScript中我们直接很容易的就能创建一个空对象,然后用键值对封装每个每个策略类,所以,我们直接上代码,看起来比传统代码清晰多了
let performanceObj = {
"S": function (salary) {
return salary * 4
},
"A": function (salary) {
return salary * 3
},
"B": function (salary) {
return salary * 2
}
}
function getBonus(performanceLevel, salary) {
return performanceObj[performanceLevel](salary)
}
结果毋庸置疑的是正确的的,扯句题外话,其实这两种实现方式在逻辑上是没什么太大区别的,主要是因为语言的差异性(这里就再解释一遍,上面的实现方式也是js,因为虽然是js实现,但是模拟的是传统面向对象语言的设计过程),那么那种方式实现更好呢,就这个例子而言,看上去可能JavaScript的设计更好点,但是换个场景可能就不是JavaScript更好了,每种语言都有自己的优缺点,比如传统的面向对象语言的代码维护性就比JavaScript这种的脚本语言搞很多,这也就是为什么会出现JavaScript超集Typescript这门语言了,就是为了弥补JavaScript在数据维护上的缺陷,所以大家不必要为此纠结
由于篇幅过长我将该模式分作两部分来讲,该部分先讲清楚了策略模式是什么,策略模式中包含那两部分,以及怎么通过策略模式改造代码,最后通过策略模式实现了一个基本的较为简单的案例的,下篇文中我会重点围绕表单提交案例以及多个策略作用在同一实例上的问题来继续深入探讨策略模式,注意更新噢
内容摘自《JavaScript设计模式与开发实践》· 曾探 · 著