Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.(定义一系列算法,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。)

策略模式,最常见的应用场景是,利用它来避免冗长的 if-elseswitch 分支判断。不过,它的作用还不止如此。它也可以像模板模式那样,提供框架的扩展点等等。

工厂模式是解耦对象的创建和使用,观察者模式是解耦观察者和被观察者。策略模式跟两者类似,也能起到解耦的作用,不过,它解耦的是策略的定义、创建、使用这三部分。

1 策略模式 UML

图片来源于《Android 源码设计模式解析与实战》

  • Context : 用来操作策略的类
  • Stragety:策略的抽象
  • ConcreteStragetyA:具体的策略实现

策略模式的定义

根据上图可实现如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Strategy {
void algorithm();
}

public class ConcreteStrategyA implements Strategy {
@Override
public void algorithm() {
//具体的内容...
}
}

public class ConcreteStrategyB implements Strategy {
@Override
public void algorithm() {
//具体的内容...
}
}

策略的创建

这里讲一下操作策略的类,如何创建策略,因为策略模式会包含一组策略,在使用它们的时候,一般会通过类型(type)来判断创建哪个策略来使用。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class StrategyFactory {
public static Strategy getStrategy(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("type should not be empty.");
}

if (type.equals("A")) {
return new ConcreteStrategyA();
} else if (type.equals("B")) {
return new ConcreteStrategyB();
}

return null;
}
}

为了封装创建逻辑,我们需要对客户端代码屏蔽创建细节。我们可以把根据 type 创建策略的逻辑抽离出来,放到工厂类中。
如果策略类是无状态的,不包含成员变量,只是纯粹的算法实现,这样的策略对象是可以被共享使用的,不需要在每次调用 getStrategy() 的时候,都创建一个新的策略对象。针对这种情况,我们可以使用如下方式创建,事先创建好每个策略对象,缓存到工厂类中,用的时候直接返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class StrategyFactory {
private static final Map<String, Strategy> strategies = new HashMap<>();

static {
strategies.put("A", new ConcreteStrategyA());
strategies.put("B", new ConcreteStrategyB());
}

public static Strategy getStrategy(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("type should not be empty.");
}
return strategies.get(type);
}
}

如果策略类是有状态的,根据业务场景的需要,我们希望每次从工厂方法中,获得的都是新创建的策略对象,而不是缓存好可共享的策略对象,那就不可以用这种方式,还是得用第一种 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
32
33
34
35
36
37
38
39
40
41
42
public class PriceCalculator {
//公交车
private static final int BUS = 1;
//地铁
private static final int SUBWAY = 2;

private int busPrice(int km) {
// 10 公里起步价 5 元
int extraTotal = km - 10;
// 超过 10 公里,每 5 公里收 2 元,超过几个 5 公里
int extraFactor = extraTotal / 5;
// 查看最后是否有不满 5 公里。
int fraction = extraTotal % 5;
// 起步价 + 超出部分
int price = 5 + extraFactor * 2;
// 最后不满 5 公里,只要多出部分大于一公里按 5 公里计算, 比如 (24-10) 14 / 5 = 2 . 剩下的 4 公里按 5 公里算
return fraction > 1 ? price + 2 : price;
}

private int subwayPrice(int km) {
if (km < 6) {
return 3;
} else if (km >= 6 && km < 12) {
return 4;
} else if (km >= 12 && km < 22) {
return 5;
} else if (km >= 22 && km < 32) {
return 6;
} else {
return 7;
}
}

int calculatePrice(int km, int type) {
if (type == BUS) {
return busPrice(km);
} else if (type == SUBWAY) {
return subwayPrice(km);
}
return 0;
}
}

代码写完了,也能用,但是,如果现在添加一个 TAXI 类型,又得在 PriceCalculator 类中修改、添加很多代码,很明显这里并没有遵循单一职责,而且使用了 if - else 判断使用哪种计算方式,再添加时,只能再往后添加 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public interface CalculateStrategy {
/**
* 按距离计算价格
* @param km 公里
* @return 价格
*/
int calculatePrice(int km);
}

public class BusStrategy implements CalculateStrategy {

@Override
public int calculatePrice(int km) {
// 10 公里起步价 5 元
int extraTotal = km - 10;
// 超过 10 公里,每 5 公里收 2 元,超过几个 5 公里
int extraFactor = extraTotal / 5;
// 查看最后是否有不满 5 公里。
int fraction = extraTotal % 5;
// 起步价 + 超出部分
int price = 5 + extraFactor * 2;
// 最后不满 5 公里,只要多出部分大于 1 公里按 5 公里计算, 比如 (24-10) 14 / 5 = 2 . 剩下的 4 公里按 5 公里算
return fraction > 1 ? price + 2 : price;
}
}

public class AlgRange {
private long start;
private long end;
private int alg;

public AlgRange(long start, long end, int alg) {
this.start = start;
this.end = end;
this.alg = alg;
}

public int getAlg() {
return alg;
}

public boolean inRange(long size) {
return size >= start && size < end;
}
}

public class SubWayStrategy implements CalculateStrategy {
private static final List<AlgRange> algs = new ArrayList<>();

static {
algs.add(new AlgRange(0, 6, 3));
algs.add(new AlgRange(6, 12, 4));
algs.add(new AlgRange(12, 22, 5));
algs.add(new AlgRange(22, 32, 6));
algs.add(new AlgRange(32, Long.MAX_VALUE, 7));
}

@Override
public int calculatePrice(int km) {
int price = 0;
for (AlgRange algRange : algs) {
if (algRange.inRange(km)) {
price = algRange.getAlg();
break;
}
}
// 由于这里直接返回了所需要的的结果,还有可能是某个对象,在这里调用父类方法,达到策略选择的目的。
return price;
}
}

这里只贴出了抽象和实现类,由于 SubWayStrategy 实现时,使用了大量的 if - else, 所以这里也可以使用策略模式整理代码,具体请看代码。
最后看看操作策略的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class PriceCalculator {
//公交车
private static final int BUS = 1;
//地铁
private static final int SUBWAY = 2;

private static final Map<Integer, CalculateStrategy> algs = new HashMap<>();

static {
algs.put(BUS, new BusStrategy());
algs.put(SUBWAY, new SubWayStrategy());
}

int calculatePrice(int km, int type) {
return algs.get(type).calculatePrice(km);
}
}

这样就解决掉了所有 if - else ,并且在添加新的策略的时候不会影响到之前的逻辑代码。

Android 中的策略模式

我们对动画设置不同的插值器(TimeInterpolator ) ,可以实现不同的动态效果

Animation 就类似于上面的操作策略的类,之前都是提前初始化好实现类,但是在 Interpolator 是需要设置的

1
2
3
public void setInterpolator(Interpolator i) {
mInterpolator = i;
}

在使用的时候会根据设置的实现类调用相应的方法。

1
2
3
4
5
public boolean getTransformation(long currentTime, Transformation outTransformation) {
//...
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
//...
}

这里简单看一下插值器结构图。

图片来源于《Android 源码设计模式解析与实战》

感谢

设计模式之美

《Android 源码设计模式解析与实战》

以及上文中的链接