JS设计模式之策略模式
前言
不知道你有没有遇到过这样的情况,对不同的输入需要产生不同的输出,例如我现在需要根据员工的等级来发最后的年终奖,这时候最简便的方法莫过于写一大堆if···else···
语句了。就像这样:
var calculateBonus = function( level, salary ){
if ( level === 'S' ){
return salary * 4;
}
if ( level === 'A' ){
return salary * 3;
}
if ( level === 'B' ){
return salary * 2;
}
};
calculateBonus( 'B', 20000 ); // 输出:40000
calculateBonus( 'S', 6000 ); // 输出:24000
可以看到这样写的确很简单,但是存在着显而易见的问题:
- 整个calculateBouns函数太庞大了,包含了非常多的
if-else
语句,这些语句需要覆盖所有的语句。 - 违背了开放封闭原则。如果需要临时增加员工等级或者修改分配方式,那么只能直接去修改原来的代码,增加判断条件,这种侵入是很大的。
- 重用性低,这样的代码无法高效复用,只能无脑CV。
这个时候,我们需要一种更好的代码组织方式。
策略模式
什么是策略模式呢?策略模式就是定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换,那么这是比较官方的说法。代入我们这个计算奖金的例子里面来讲的话,每个if语句里面的逻辑就相当于算法,我们要把这些算法用一些手段封装起来,让它们之间独立,保持一个平等的关系也就相当于它们之间可以相互替换。
使用策略模式重构代码
大概知道了策略模式大概是怎么回事。接下来,具体来探究如何使用策略模式。首先,策略模式至少由两部分组成,第一个是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个是Context(上下文),Context负责接收请求,之后把请求委托给某一个具体的策略类。
其实很好理解,Context就相当于一个中转站,接收不同的计算请求,然后把具体的计算任务转发给某一个具体策略类。那么接下来就使用策略模式来讲上面讲的那个计算年终奖的例子来实现吧
定义一组策略类
策略类往往都是指一个问题中可能会变化的部分。回到年终奖的例子中,计算年终奖的方式都不同,他的级别高一点,年终奖就高一点,她的级别可能低一点,年终奖可能就低一点。那么,计算年终奖的方式就是变化的,策略类的任务就是把这些不同的计算方式封装成一组一组的策略。
let LevelS = function(){};
performanceS.prototype.calculate = function( salary ){
return salary * 4;
};
let LevelA = function(){};
performanceA.prototype.calculate = function( salary ){
return salary * 3;
};
let LevelB = function(){};
performanceB.prototype.calculate = function( salary ){
return salary * 2;
};
上面的代码定义了三个构造函数,分别对应三种不同的等级并且在各自的原型上定义了calculate
方法,这个方法用来计算年终奖。
实现中转站
中转站就相当前面提到的策略模式的第二部分Context,其实它就是一个向外暴露的接口,根据不同的输入去调用不同的策略类,然后将结果返回。
实现的方式也很简单,这里还是使用类,先定义一个奖金类Bouns,它有两个原型方法,setSalary
用来设置原始工资,setStrategy
用来设置策略对象,最后,getBouns
用来计算年终奖,Bouns类本身不具备计算能力,当调用getBouns方法的时候其实是将计算任务委派给了对应的策略类。
let Bouns = function () {
this.salary = null;
this.strategy = null
}
Bouns.prototype.setSalary = function (salary) {
// 设置员工的原始工资
this.salary = salary;
}
Bouns.prototype.setStrategy = function (strategy) {
// 设置策略对象
this.strategy = strategy;
}
Bouns.prototype.getBouns = function () {
return this.strategy.calculate(this.salary);
}
实现策略模式年终奖计算
let bouns = new Bouns()
bouns.setSalary(20000);
bouns.setStrategy(new LevelS())
console.log(bouns.getBouns()) // 输出: 80000
我们先创建了一个bouns
对象,然后给这个对象设置了一些基本的数据,基本工资和对应等级的策略对象,然后通过getBouns
方法来计算年终奖。如果想计算其他等级的年终奖,只需要继续调用setStrategy
方法设置其他的策略对象即可,不需要更改原来的代码。如果想增加等级就更简单了,直接新建一个策略类,然后调用setStrategy
方法将新建的策略的对象传入就可以了。
这样的代码是不是要比之前的if-else
要好很多呢,变得更好维护了,重用性也高了。
JavaScript版本的策略模式
看到这个标题是不是很不解,难道上面写的不是JavaScript?这是因为上面的策略模式是模仿一些传统的面向对象编程语言的策略模式的实现,而在JavaScript中对象不一定需要构造函数实例化,而且函数也是对象。完全可以将那一组组的策略类变成一个普通对象的属性,把中转站变成一个普通函数啊,这样岂不是更简单明了。
创建策略对象
创建一个策略对象,这个策略对象里面包含了不同的计算策略,其实也就是对象的方法
let strategies = {
'S': function (salary) {
return salary * 4
},
'A': function (salary) {
return salary * 3
},
'B': function (salary) {
return salary * 2
},
}
我们将不同的计算方法放到了一起作为对象的方法,就相当于把他们组合放置在一个篮子里,之后需要哪个就去拿哪个。
创建一个计算函数
function calculateBouns (level, salary) {
return strategies[level](salary);
}
console.log(calculateBouns('S', 20000))
calculateBouns
函数接收等级和基础工资两个参数,然后去strategies对象中找到与等级相对应的方法求值。相比使用类的方式实现策略模式,这样更清晰明了。再相比最开始直接使用if-else
的方法,省去了条件判断,增加了程序的”弹性“,易扩展。
总结
策略模式其实也有体现出多态性,我们没有把和年终奖计算相关的逻辑放在一起,而是分布在各个策略对象中。而Context中转站没有计算能力,而是将具体的计算任务交给某个策略对象,然后策略对象返回不同的结果,这正是多态性的体现,也是它们之间可以相互替换的关键,替换Context中转站中保存的策略对象,就可以得到不同的计算结果。策略模式的优点很明显:
- 可以有效地避免多重条件选择语句
- 代码复用性高,避免了很多粘贴复制的操作。
- 策略模式提供了对开放封闭原则的支持,将算法独立封装在strategies中,使得它们易于切换,易于扩展。