设计模式 - 策略模式
策略模式的定义
策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。
一个例子
在写策略模式之前,先看一个日常生活中常见的小例子: 我们在电商平台买东西的时候,平台一般会根据不同的用户制定不同的报价策略,比如新用户立减 10 元,VIP 客户免配送费,SVIP 客户 8 折优惠,现在我们要做一个结算模块,针对不同客户计算不同价格。
对于这个需求,我们很有可能写出这样的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class PriceManager {
public int calculatePrice(int totalPrice, int shippingFee, String userType) {
int finalPrice = totalPrice;
switch (userType) {
case "new":
finalPrice = totalPrice - 10;
break;
case "vip":
finalPrice = totalPrice - shippingFee;
break;
case "svip":
finalPrice = (int)(totalPrice * 0.8);
break;
default:
break;
}
return finalPrice;
}
}
简单来看,上面的代码可以满足需求,但是其实是存在问题的,它把不同的价格算法都写在一个方法里面,随着业务量的增多,这个方法将会变得越来越庞大,越来越难维护。针对这个情况,可以对这个方法进行改进,把不同的算法独立成一个方法。
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
public class PriceManager {
public int calculatePrice(int totalPrice, int shippingFee, String userType) {
int finalPrice = totalPrice;
switch (userType) {
case "new":
finalPrice = calculatePrice4New(totalPrice, shippingFee);
break;
case "vip":
finalPrice = calculatePrice4Vip(totalPrice, shippingFee);
break;
case "svip":
finalPrice = calculatePrice4SVip(totalPrice, shippingFee);
break;
default:
finalPrice = calculatePrice4Default(totalPrice, shippingFee);
break;
}
return finalPrice;
}
private int calculatePrice4New(int totalPrice, int shippingFee) {
return totalPrice - 10;
}
private int calculatePrice4Vip(int totalPrice, int shippingFee) {
return totalPrice - shippingFee;
}
private int calculatePrice4SVip(int totalPrice, int shippingFee) {
return (int) (totalPrice * 0.8);
}
private int calculatePrice4Default(int totalPrice, int shippingFee) {
return totalPrice;
}
}
经过上面的修改,代码相对好了一点,如果某一个具体的算法规则改变了,我们只需要修改对应的方法即可。但是,还存在另外的问题:目前的用户类型只有四种,如果要新增用户类型同时新增价格算法,那么就必须修改计算价格的方法,在方法中增加分支,长此以往,方法中的分支将会变得非常多,而且需要经常修改。
这时候策略模式就派上用场了,使用策略模式可以使价格计算既可扩展、可维护,又可以方便的响应变化。
策略模式的一般结构
- 策略接口 IStrategy: 用来抽象一系列策略算法
- 具体的策略实现 StrategyImpl: 根据具体情况实现 IStrategy 中定义的算法
- 策略上下文 StrategyContext: 负责具体的策略交换,决定具体使用哪一个策略,通常 StrategyContext 会通过构造方法持有一个真正的策略实现对象
- 外部客户端通过 StrategyContext 指定具体的 IStrategy 从而实现不同策略的切换
代码示例:
1
2
3
4
5
6
7
/**
* 策略接口
*/
public interface IStrategy {
public void doSth();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 策略实现A
*/
public class StrategyImplA implements IStrategy {
@Override
public void doSth() {
System.out.println("StrategyImplA doSth");
}
}
/**
* 策略实现B
*/
public class StrategyImplB implements IStrategy {
@Override
public void doSth() {
System.out.println("StrategyImplB doSth");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 策略上下文
*/
public class StrategyContext {
private IStrategy iStrategy;
public StrategyContext(IStrategy iStrategy) {
this.iStrategy = iStrategy;
}
public void doSth() {
this.iStrategy.doSth();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 外部客户端调用
*/
public class StrategyClient {
public static void main(String[] args) {
StrategyContext context;
System.out.println("使用 StrategyImplA");
context = new StrategyContext(new StrategyImplA());
context.doSth();
System.out.println("使用 StrategyImplB");
context = new StrategyContext(new StrategyImplB());
context.doSth();
}
}
通过上面对策略模式的一般结构实现的了解,我们可以依葫芦画瓢,改造价格计算模块。
例子改造
首先分析一下价格计算模块与策略模式例子的一一对应关系
1
2
3
IStrategy:抽象价格计算方法
StrategyImpl:不同类型用户实现不同的价格算法
StrategyContext:价格计算上下文
写成代码大概是这样:
1
2
3
4
5
6
7
/**
* 价格计算策略接口
*/
public interface IPriceStrategy {
public void calculatePrice(int totalPrice, int shippingFee);
}
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
/**
* 新用户价格计算策略实现
*/
public class NewPriceStrategy implements IPriceStrategy {
@Override
public void calculatePrice(int totalPrice, int shippingFee) {
System.out.println("新用户立减 10 元");
return totalPrice - 10;
}
}
/**
* VIP 用户价格计算策略实现
*/
public class VipPriceStrategy implements IPriceStrategy {
@Override
public void calculatePrice(int totalPrice, int shippingFee) {
System.out.println("VIP 用户免配送费");
return totalPrice - shippingFee;
}
}
/**
* SVIP 用户价格计算策略实现
*/
public class VipPriceStrategy implements IPriceStrategy {
@Override
public void calculatePrice(int totalPrice, int shippingFee) {
System.out.println("SVIP 用户 8 折");
return (int)(totalPrice * 0.8);
}
}
/**
* 普通用户价格计算策略实现
*/
public class DefaultPriceStrategy implements IPriceStrategy {
@Override
public void calculatePrice(int totalPrice, int shippingFee) {
System.out.println("普通用户无优惠");
return totalPrice;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 价格计算上下文
*/
public class PriceStrategyContext {
private IPriceStrategy iStrategy;
public PriceStrategyContext(IPriceStrategy iStrategy) {
this.iStrategy = iStrategy;
}
public void calculatePrice(int totalPrice, int shippingFee) {
this.iStrategy.calculatePrice(totalPrice, shippingFee);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 外部客户端调用
*/
public class PriceStrategyClient {
public static void main(String[] args) {
int totalPrice = 100, shippingFee = 10;
int finalPrice;
PriceStrategyContext context;
System.out.println("新用户结算:");
context = new PriceStrategyContext(new NewPriceStrategy());
finalPrice = context.calculatePrice(totalPrice, shippingFee);
System.out.println("新用户结算价格:" + finalPrice);
System.out.println("VIP 用户结算:");
context = new PriceStrategyContext(new VipPriceStrategy());
finalPrice = context.calculatePrice(totalPrice, shippingFee);
System.out.println("VIP 用户结算价格:" + finalPrice);
}
}
上面的代码使用策略模式同样实现价格计算模块,接着可以利用策略模式解决前面的提到的问题了。
假如新增了一个用户类型 SSVIP , 价格算法是可以享受免配送费之后 8 折优惠。这时候需要做的就是新增一个 SSVIP 的价格计算策略,然后外部客户端调用的时候采用 SSVIP 策略即可,代码如下:
1
2
3
4
5
6
7
8
9
10
/**
* SSVIP 用户价格计算策略实现
*/
public class SSVipPriceStrategy implements IPriceStrategy {
@Override
public void calculatePrice(int totalPrice, int shippingFee) {
System.out.println("SSVIP 用户免配送费后 8 折");
return (int)((totalPrice - shippingFee) * 0.8);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 外部客户端调用
*/
public class PriceStrategyClient {
public static void main(String[] args) {
int totalPrice = 100, shippingFee = 10;
int finalPrice;
PriceStrategyContext context;
System.out.println("SSVIP 用户结算:");
context = new PriceStrategyContext(new SSVipPriceStrategy());
finalPrice = context.calculatePrice(totalPrice, shippingFee);
System.out.println("SSVIP 用户结算价格:" + finalPrice);
}
}
优缺点
优点:
- 策略模式的功能就是通过抽象、封装来定义一系列的算法,使得这些算法可以相互替换,所以为这些算法定义一个公共的接口,以约束这些算法的功能实现。如果这些算法具有公共的功能,可以将接口变为抽象类,将公共功能放到抽象父类里面。
- 策略模式的一系列算法是可以相互替换的、是平等的,写在一起就是 if-else 或者 switch-case 组织结构,如果算法实现里又有条件语句,就构成了多重条件语句,可以用策略模式,避免这样的多重条件语句。
- 易于扩展:在策略模式中扩展策略实现非常的容易,只要新增一个策略实现类,然后在使用策略实现的地方,使用这个新的策略实现就好了。
缺点:
- 客户端或者调用方必须了解所有的策略,清楚各个策略之间的不同,这样客户端才能选择正确的策略。
- 增加了对象的数量,由于每个策略都是一个实现类,如果可选的策略很多的话,那对象的数量也会增多。
应用场景
- 需要在多种算法中动态的选择一种的情况下可以使用
- 不需要客户知道复杂的、与算法相关的数据结构时可以使用
- 使用第三方库时,想要快速方便切换同类型的其他库,例如 Android 开发中的图片加载框架,可以使用策略模式封装一套 Glide 的策略,一套 Picasso 的策略