什么是策略模式
定义
策略模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。
策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
简而言之,就是策略模式准备了一组算法,并将每个算法进行封装,使它们之间可用相互替换。
策略模式除了用来封装算法,也可以用来封装一系列的”业务规则”,只要这些业务规则指向的目标一致,并且可以被替换使用,我们就可以用策略模式来封装它们。
相关概念
一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,第二个部分是环境类Context。
- 策略类:策略类封装了具体的算法,并负责具体的计算过程。
- 环境类Context:环境类Context接受客户的请求,随后把请求委托给某一个策略类。
基于传统面向对象语言的方式使用策略模式
尝试开一家猫咖
我们举一个猫咖的例子,假定我们开了一家猫咖,需要举办周年庆典,其中一项就是为我们猫咖的会员,在周年庆期间充值预存款,根据不同的VIP级别加赠不同比例的赠款余额。
例如,普通会员(regular)赠送价值预存款10%的赠款余额,金卡会员(gold)赠送价值预存款20%的赠款余额,白金卡会员(platinum)赠送价值预存款30%的赠款余额。
通常的逻辑,我们会想到使用if-else来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
let calculateBonus = (vipLevel,deposit) => { if (vipLevel === 'regular') { return deposit * 0.1 } if (vipLevel === 'gold') { return deposit * 0.2 } if (vipLevel === 'platinum') { return deposit * 0.3 } };
calculateBonus('gold', 500)
calculateBonus('platinum', 2000)
|
这样的代码会有以下三个问题:
- calculateBonus函数比较庞大,包含需要if-else语句,难以维护。
- calculateBonus函数缺乏灵活的弹性,如果需要增加钻石卡会员(diamond)的赠款策略,还需要通读函数内部实现,违反了开放-封闭原则。
- 赠款算法无复用性,在程序的其他地方需要重用,只能复制粘贴。
为了解决这三个问题中的第三个赠款算法无复用性的问题,我们可以尝试使用复合函数进行解决。
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
| let regular = (deposit) => { return deposit * 0.1; }
let gold = (deposit) => { return deposit * 0.2; }
let platinum = (deposit) => { return deposit * 0.3; }
let calculateBonus = (vipLevel,deposit) => { if (vipLevel === 'regular') { return regular(deposit); } if (vipLevel === 'gold') { return gold(deposit); } if (vipLevel === 'platinum') { return platinum(deposit); } };
calculateBonus('gold', 500)
calculateBonus('platinum', 2000)
|
这样也仅仅解决了赠款算法在程序其他地方需要服用的问题,仍然还存在着其它两个无法解决的问题。
因此,我们可以使用策略模式来重构代码。
使用策略模式重构猫咖周年庆预存活动
ES5实现
完整代码
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
|
var RegularCard = function () { };
RegularCard.prototype.calculate = function (deposit) { return deposit * 0.1; }
var GoldCard = function () { };
GoldCard.prototype.calculate = function (deposit) { return deposit * 0.2; }
var PlatinumCard = function () { };
PlatinumCard.prototype.calculate = function (deposit) { return deposit * 0.3; }
var Bonus = function(){ this.deposit = null; this.strategy = null; };
Bonus.prototype.setSalary = function( deposit ){ this.deposit = deposit; };
Bonus.prototype.setStrategy = function( strategy ){ this.strategy = strategy; };
Bonus.prototype.getBonus = function(){ return this.strategy.calculate( this.deposit ); };
|
测试用例
1 2 3 4 5 6 7 8 9
| var bonus = new Bonus();
bonus.setSalary( 2000 ); bonus.setStrategy( new GoldCard() );
console.log( bonus.getBonus() );
bonus.setStrategy( new PlatinumCard() ); console.log( bonus.getBonus() );
|
ES6实现
完整代码
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
|
class RegularCard { calculate(deposit) { return deposit * 0.1; } }
class GoldCard { calculate(deposit) { return deposit * 0.2; } }
class PlatinumCard { calculate(deposit) { return deposit * 0.3; } }
class Bonus { constructor() { this.deposit = null; this.strategy = null; } setSalary( deposit ) { this.deposit = deposit; } setStrategy( strategy ) { this.strategy = strategy; }; getBonus() { return this.strategy.calculate( this.deposit ); }; }
|
测试用例
1 2 3 4 5 6 7 8 9
| let bonus = new Bonus();
bonus.setSalary( 2000 ); bonus.setStrategy( new GoldCard() );
console.log( bonus.getBonus() );
bonus.setStrategy( new PlatinumCard() ); console.log( bonus.getBonus() );
|
基于JavaScript语言使用策略模式
Peter Norvig在他的演讲中曾说过:“在函数作为一等对象的语言中,策略模式是隐形的。策略类strategy
就是值为函数的变量。”
上述策略模式的实现是模拟传统面向对象语言的实现,由于在JavaScript中,函数也可以作为对象的value值成员,所以更方便的做法是使用对象字面量实现策略模式的策略类,同理,环境类(在这里是奖金类),也可以单独使用calculateBonus
函数来接受用户的请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
let strategies = { "RegularCard": function( deposit ){ return deposit * 0.1; }, "GoldCard": function( deposit ){ return deposit * 0.2; }, "PlatinumCard": function( deposit ){ return deposit * 0.3; } };
let calculateBonus = (vipLevel,deposit) => { return strategies[vipLevel](deposit); }
console.log('GoldCard', 2000) console.log('RegularCard', 1000)
|
优点与缺点
优点
- 多重条件语句(if-else)不易维护,策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
- 在策略模式中利用组合和委托来让环境类拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
- 策略模式可以提供相同行为的不同实现,更容易满足需求的多变。
- 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的策略类
strategy
中,使得它们易于切换,易于理解,易于扩展。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
缺点
- 使用策略模式会在程序中增加许多策略类或者策略对象,但实际上这比把它们负责的逻辑堆砌在环境类中要好。
- 使用者需要理解所有策略算法
strategy
的区别,以便使用合适的策略算法。(比如,我们要选择一种合适的旅游出行路线,必须先了解选择飞机、火车、自行车等方案的细节。此时策略类strategy
要向使用者暴露它的所有实现,这是违反最少知识原则的)