时势造英雄-策略模式

策略模式

针对同一类情况, 如果因为某个或多个因素和有不同的处理方法, 一般这种就会造成写很多的if else, 甚至嵌套. 策略模式就是针对这种情况的一种让代码遵循开闭原则的方案.

适用场景

  • 针对同一类型问题的多种处理方式, 仅仅是具体行为有差别时
  • 需要安全的封装同一类型的操作时
  • 出现同一个抽象有多个子类, 而又要使用 if else 来判断具体实现时

类图

  • Context: 持有策略的上下文
  • Strategy: 策略的抽象
  • StrategyA, StrategyB 具体的策略实现

优点

通过将同一类型问题的处理方式抽象, 然后由具体类实现行为. 在需要使用不同的方式处理时, 将具体实现类注入到持有策略的上下文中. 避免将不同处理方式通过 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
24
25
26
27
28
29
30
31
public class ElectricCalculator {

enum Type {
V220, // 民用电一般为 220V
V380 // 商用电压一般为三厢380V
}

public double calculate(float deg, Type type) {
if (type == Type.V220) {
return calculateV220(deg);
}
if (type == Type.V380) {
return calculateV380(deg);
}
return 0;
}

private double calculateV220(float deg) {
return deg * 0.5;
}

private double calculateV380(float deg) {
return deg * 0.86;
}

public static void main(String[] args) {
ElectricCalculator calculator = new ElectricCalculator();
double feeV220 = calculator.calculate(100, Type.V220); // 计算民用电
double feeV380 = calculator.calculate(100, Type.V380); // 计算工业用电
}
}

现在这个类里的逻辑还比较简单, 看起来没什么问题. 如果加上电费的梯度收费、分季节收费, 再加上支持租房用电费计算, 那这个类的逻辑就有点乱了.

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
public class ElectricCalculator {

enum Type {
V220, // 民用电一般为 220V
V380, // 商用电压一般为三厢380V
RENT // 租房用电一般不同于民用电收费
}

/**
* @param deg 本月当前用电
* @param type 用电类型
* @param isSummer 是否夏季
*/
public double calculate(float deg, Type type, boolean isSummer) {
if (type == Type.V220) {
return calculateV220(deg, isSummer);
}
if (type == Type.V380) {
return calculateV380(deg, isSummer);
}
if (type == Type.RENT) {
return calculateRent(deg, isSummer);
}
return 0;
}

// 模拟梯度收费和分夏季收费
private double calculateV220(float deg, boolean isSummer) {
float rate = 0.5f;
if (deg > 500) {
rate = 0.6f;
}
if (deg > 800) {
rate = 0.7f;
}
if (isSummer) {
rate += 0.1f;
}
return deg * rate;
}

private double calculateV380(float deg, boolean isSummer) {
return deg * 0.86;
}

private double calculateRent(float deg, boolean isSummer) {
return deg * 1;
}
}

现在支持了梯度收费和分季节收费, 而且每个类型的电费计算方式又不一样, 这个类里就会包含很多的分支逻辑. 且当需要修改或添加一种电费类型时, 很容易造成错误.
这种情况下, 就适合使用策略模式, 将电费的计算方法抽象化, ElectricCalculator 中的计算方法的具体实现由外部注入.

重构

策略抽象

1
2
3
interface Calculator {
double calculate(float deg);
}

实现不同策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class V220Calculator implements Calculator {

@Override
public double calculate(float deg) {
float rate = 0.5f;
if (deg > 600) {
rate = 0.6f;
}
if (deg > 900) {
rate = 0.7f;
}
return deg * rate;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 针对夏季的
class V220SummerCalculator implements Calculator {

@Override
public double calculate(float deg) {
float rate = 0.5f;
if (deg > 500) {
rate = 0.6f;
}
if (deg > 800) {
rate = 0.7f;
}
return deg * rate;
}
}

注入策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ElectricCalculator {
private Calculator mCalculator;

public void setCalculator(Calculator calculator) {
mCalculator = calculator;
}

public double calculate(float deg) {
if (mCalculator == null) {
throw new IllegalStateException("Calculator must not null");
}
return mCalculator.calculate(deg);
}

public static void main(String[] args){
ElectricCalculator calculator = new ElectricCalculator();
calculator.setCalculator(new V220Calculator());
double fee = calculator.calculate(100);
calculator.setCalculator(new V220SummerCalculator());
double feeSummer = calculator.calculate(10);
}
}

重构之后

可以看出, ElectricCalculator 类中的逻辑变简单了, 而且每个不同的分支由不同的策略实现类代替, 保证了增加新策略的扩展性和修改代码的封闭性(不用修改ElectricCalculator中代码).