策略模式
针对同一类情况, 如果因为某个或多个因素和有不同的处理方法, 一般这种就会造成写很多的if
else
, 甚至嵌套. 策略模式就是针对这种情况的一种让代码遵循开闭原则的方案.
适用场景
- 针对同一类型问题的多种处理方式, 仅仅是具体行为有差别时
- 需要安全的封装同一类型的操作时
- 出现同一个抽象有多个子类, 而又要使用
if
else
来判断具体实现时
类图
@startuml
class Context {
+algorithm()
+setStrategy(Strategy)
}
interface Strategy {
+algorithm()
}
class StrategyA {
+algorithm()
}
class StrategyB {
+algorithm()
}
Context o- Strategy
Strategy <|-- StrategyA
Strategy <|-- StrategyB
@enduml
- 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
中的计算方法的具体实现由外部注入.
重构
@startuml
class ElectricCalculator {
+calculate(int deg)
+setCalculator(Calculator)
}
interface Calculator {
+calculate(int deg)
}
class V220Calculator {
+calculate(int deg)
}
class V220SummerCalculator {
+calculate(int deg)
}
ElectricCalculator o- Calculator
Calculator <|-- V220Calculator
Calculator <|-- V220SummerCalculator
@enduml
策略抽象
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
中代码).