JS设计模式——策略模式

前言

本系列为阅读曾探的《JavaScript设计模式与开发实践》一书所做的读书笔记,大部分内容摘自原书,加入了部分个人理解。本篇内容为书中第5章策略模式。

策略模式

策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

可用于解决一些包含多种条件语句 if-else 的问题,同时提高代码的复用性。

策略模式的目的就是将算法的使用与算法的实现分离开来。一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。 第二个部分是环境类Context,Context 接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明Context 中要维持对某个策略对象的引用。

举例,年终奖金计算。

直观简单的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
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 ); // 输出:40000
calculateBonus( 'S', 6000 ); // 输出:24000

缺点:

  1. 大量if-else判断语句
  2. 函数缺乏弹性、扩展性
  3. 代码复用性差

使用组合函数

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 ); // 输出:30000

增强了代码复用性,但仍具有上述问题。

仿面向对象语言的策略模式实现

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
// 封装了奖金计算规则的三个策略类
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 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); //将计算奖金的操作委托给对应的策略对象
}
// Context对象bouns
var bouns = new Bouns();
bonus.setSalary( 10000 );
bonus.setStrategy( new performanceS() ); // 设置策略对象
console.log( bonus.getBonus() ); // 输出:40000
bonus.setStrategy( new performanceA() ); // 设置策略对象
console.log( bonus.getBonus() ); // 输出:30000

清楚地将之前的代码根据不同的作用进行了重构,分割成多个类。

Js 语言下的策略模式实现

在 Js 语言中,函数也是对象。上一节代码实现了3个策略类,可以用函数代替。Context 类也不需要使用 Bouns 类实现,使用calculateBonus函数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000

这样一来,代码更加简介。对于不同的需求,只需要修改 strategies 对象即可。对于缓动动画和表单验证等问题,我们也可以采用类型的方式实现。

从定义上看,策略模式就是用来封装算法的。在实际开发中,我们通常会把算法的含义扩散开来,使策略模式也可以用来封装一系列的“业务规则”。只要这些业务规则指向的目标一致,并且可以被替换使用,我们就可以用策略模式来封装它们。

策略模式的优缺点

优点

  1. 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句
  2. 策略模式提供了对开放——封闭原则的完美支持,将算法封装在独立的strategy 中,使得它们易于切换,易于理解,易于扩展。
  3. 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
  4. 在策略模式中利用组合和委托来让Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案。

缺点

  1. 使用策略模式会在程序中增加许多策略类或者策略对象,但实际上这比把它们负责的逻辑堆砌在Context 中要好。
  2. 要使用策略模式,必须了解所有的strategy,必须了解各个strategy 之间的不同点,这样才能选择一个合适的strategy。此时strategy 要向客户暴露它的所有实现,这是违反最少知识原则的。