在我们的实际业务中经常会遇到很多分支判断的情况,包括商城根据用户会员等级的促销折扣、根据绩效等级的年终奖金计算,这些情况下的每种分支的业务都是类似的,只是对于具体业务的具体处理过程或者算法的不同,导致最终的效果不同。各分支之间都是平级关系,这种情况下我们可以采用策略模式,来解决算法和使用者之间的耦合。
策略模式的定义是:定义一系列的算法,把它们一个一个封装起来,并且使它们可以相互替换。
使用策略模式计算奖金
最原始的代码实现
我们编写一个名为 calculateBonus
的函数来计算每个人的年终奖金。显然,改函数需要两个参数:员工的工资数额和他的绩效考核等级。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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 ); calculateBonus( 'S', 6000 );
|
可以看出元时代吗中存在着显而易见的缺点:
calculateBonus
函数会随着绩效考核等级的增加而不断变得臃肿起来, if-else
语句会越变越多;
calculateBonus
函数缺乏弹性,每增加一种绩效等级或者是改变一个绩效等级的奖金系数,我们都必须深入到函数内部去进行查找并修改;
- 函数的复用性差。
因此我们就需要重构这一部分代码。
使用组合函数的方式重构代码
重构最简单的方式就是使用组合函数来进行,我们把各种算法封装到一个一个的小函数中,使用一些良好的命名规范,可以一目了然地知道它对应的算法,他们也可以复用到程序的其他地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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 );
|
采用组合函数的方式我们解决了功能复用的问题,但是还是没有解决函数变得越来越臃肿的可能以及缺乏弹性的事实。
使用策略模式
正如上文中提到的策略模式的定义:定义一系列的算法,把它们一个一个封装起来。将不变与变的部分隔离开是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将函数的使用和函数的实现隔离开。
一个基于策略模式的程序至少由两部分组成,第一部分是预测策略类,用来封装具体的算法,并负责计算过程;第二部分是环境context
类,接受到用户的请求后,随后将该请求委托给具体的某一个策略类。所以在 context
中要维持对某个策略对象的引用。
1 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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| 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; };
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.getBonus = function(){ return this.strategy.calculate( this.salary ); };
var bonus = new Bonus(); bonus.setSalary( 10000 );
bonus.setStrategy( new performanceS() ); console.log( bonus.getBonus() ); bonus.setStrategy( new performanceA() ); console.log( bonus.getBonus() ); ```
  上文中的 `context` 类就是 `bonus` , `performanceX` 类就是一个策略类,代表绩效等级 `X` 的策略方法。   虽然已经对于代码进行了相应的重构,不过该段代码是基于传统的面向对象语言来实现的,在 `JavaScript` 中有更加简单的方式去实现。
## JavaScript中的策略模式   Talk is cheap,show you code. ```js var strategies = { "S": function( salary ){ return salary * 4; }, "A": function( salary ){ return salary * 3; }, "B": function( salary ){ return salary * 2;
} }; var calculateBonus = function( level, salary ){ return strategies[ level ]( salary ); };
console.log( calculateBonus( 'S', 20000 ) ); console.log( calculateBonus( 'A', 10000 ) );
|
策略模式的优缺点
测试模式是一种有效的设计模式,在上述的描述中,我们不难看出策略模式的一些优缺点。
优点
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句;
- 策略模式封装了一些策略类,将算法封装在独立的
strategy
中,使得它们之间易切换、理解、扩展;
- 策略模式中的策略类易于复用,从而避免了许多复制粘贴修改的工作;
- 使用组合和委托来让
context
类拥有执行算法的能力,这也是继承的一种更有效的替代方案。
缺点
- 选择使用的
strategy
的决定权在用户,所以用户必须了解所有的 strategy
的作用,这样才能找到一个合适的 strategy
,这增加了使用成本;
- 因为策略模式中的
strategy
都是独立封装的,所以一些复杂算法中处理相同逻辑部分也不能公用,所以我们需要采用一些其他的方案来辅助。
参考:
- JavaScript设计模式与开发实践
- JavaScript设计模式