Appearance
实现
绩效为 S 的人年终奖有 4 倍工资,绩效为 A 的人年终奖有 3 倍工资,而绩效为 B 的人年终奖 是 2 倍工资。假设财务部要求我们提供一段代码,来方便他们计算员工的年终奖。
javascript
const calculateBonus = function(performanceLevel, salary){
if ( performanceLevel === 'S' ){
return salary * 4
}
if ( performanceLevel === 'A' ){
return salary * 3
}
if ( performanceLevel === 'B' ){
return salary * 2
}
}这段代码十分简单,但是存在着显而易见的缺点。第一是 calculateBonus 函数比较庞大,包含了 很多 if-else 语句,这些语句需要覆盖所有的逻辑分支。第二是 calculateBonus 函数缺乏弹性, 如果增加了一种新的绩效等级 C,或者想把绩效 S 的奖金系数改为 5,那我们必须深入 calculateBonus 函数的内部实现,这是违反开放-封闭原则的。第三是算法的复用性差,如果在程 序的其他地方需要重用这些计算奖金的算法呢?我们的选择只有复制和粘贴。因此,我们需要重构这 段代码。
一般最容易想到的办法就是使用组合函数来重构代码,我们把各种算法封装到一个个的小函数里面, 这些小函数有着良好的命名,可以一目了然地知道它对应着哪种算法,它们也可以被复用在程序的 其他地方。
javascript
const performanceS = function(salary){
return salary * 4
}
const performanceA = function(salary){
return salary * 3
}
const performanceB = function(salary){
return salary * 2
}
const calculateBonus = function(performanceLevel, salary){
if (performanceLevel === 'S'){
return performanceS(salary)
}
if (performanceLevel === 'A'){
return performanceA(salary)
}
if (performanceLevel === 'B'){
return performanceB(salary)
}
}程序得到了一定的改善,但这种改善非常有限,我们依然没有解决最重要的问题:calculateBonus 函数有可能越来越庞大,而且在系统变化的时候缺乏弹性。
使用策略模式来重构代码。策略模式指的是定义一系列的算法,把它们一个个封装起来。将不变的 部分和变化的部分隔开是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的 使用与算法的实现分离开来。在这个例子里,算法的使用方式是不变的,都是根据某个算法取得计 算后的奖金数额。而算法的实现是各异和变化的,每种绩效对应着不同的计算规则。一个基于策略 模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体 的计算过程。第二个部分是环境类 Context,Context 接受客户的请求,随后把请求委托给某一策略类。要做到这点,说明 Context 中要维持对某个策略对象的引用。
现在用策略模式来重构上面的代码。第一个版本是模仿传统面向对象语言中的实现。我们先把每种 绩效的计算规则都封装在对应的策略类里面。
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
};接下来定义奖金类 Bonus:
javascript
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 () { // 取得奖金数额
return this.strategy.calculate(this.salary) // 把计算奖金的操作委托给对应的策略对象
};先创建一个 bonus 对象,并且给 bonus 对象设置一些原始的数据,比如员工的原始工资数额。 接下来把某个计算奖金的策略对象也传入 bonus 对象内部保存起来。当调用 bonus.getBonus() 来计算奖金的时候,bonus 对象本身并没有能力进行计算,而是把请求委托给了之前保存好的策略 对象:
javascript
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