设计模式之策略模式

策略模式属于对象的行为模式,其用意是针对一组算法,将每一个算法封装到具有共同接口的独立类中,从而使得他们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。这样在客户端就可以通过注入不同的算法类对象进行算法的动态替换,这种模式的可扩展性和可维护性也就更高。
strategy_1.png

策略模式 UML 类图

从上图可以看到,在策略模式中有三种角色,分别是: Context 、 Strategy 、 ConcreteStrategy 。
Context : 用来操作策略的上下文环境,持有 Strategy 的引用,客户端就是将具体的策略注入到 Context 中的。
Strategy : 策略的抽象,通常是由抽象类或接口实现的,在 Java 中建议使用 接口实现。
ConcreteStrategy : 具体的策略实现类,通常有几个不同的具体策略实现类。

策略模式简单实例

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
//抽象策略类
public interface Strategy {
/**
* 策略方法
*/

public void strategyInterface();
}

//具体策略类
public class ConcreteStrategyA implements Strategy {

@Override
public void strategyInterface() {
//相关的业务
}

}

public class ConcreteStrategyB implements Strategy {

@Override
public void strategyInterface() {
//相关的业务
}

}

public class ConcreteStrategyC implements Strategy {

@Override
public void strategyInterface() {
//相关的业务
}

}

//Context 类
public class Context {
//持有一个具体策略的对象
private Strategy strategy;
/**
* 构造函数,传入一个具体策略对象
* @param strategy 具体策略对象
*/

public setStrategy(Strategy strategy){
this.strategy = strategy;
}
/**
* 策略方法
*/

public void contextInterface(){

strategy.strategyInterface();
}
}

//客户端使用
Context context = new Context();

Strategy strategyA = new ConcreteStrategyA();
context.setStrategy(strategyA).contextInterface();

Strategy strategyB = new ConcreteStrategyB();
context.setStrategy(strategyB).contextInterface();

Strategy strategyC = new ConcreteStrategyC();
context.setStrategy(strategyC).contextInterface();

可以看到客户端在使用时通过注入不同的策略类的实现对象,达到了动态替换策略的效果。

Android 源码中的策略模式

在 Android 源码中,在 View 动画的 Interpolator 和 TypeEvaluator 中就是对策略模式的典型使用,对于动画不熟悉的同学可以点击这里查看我之前的文章。

首先是 TimeInterpolator 扮演了抽象 Strategy 的角色。

1
2
3
public interface TimeInterpolator {
float getInterpolation(float input);
}

接下来是具体的 Interpolator 包括 LinearInterpolator 、 AccelerateDecelerateInterpolator 、 DecelerateInterpolator 等。
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
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
//...
public float getInterpolation(float input) {
return input;
}
//...
}

public class AccelerateDecelerateInterpolator extends BaseInterpolator
implements NativeInterpolatorFactory {

//...
@SuppressWarnings({"UnusedDeclaration"})
public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
}

public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

//...
}

public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public DecelerateInterpolator() {
}

/**
* Constructor
*
* @param factor Degree to which the animation should be eased. Setting factor to 1.0f produces
* an upside-down y=x^2 parabola. Increasing factor above 1.0f makes exaggerates the
* ease-out effect (i.e., it starts even faster and ends evens slower)
*/

public DecelerateInterpolator(float factor) {
mFactor = factor;
}

public DecelerateInterpolator(Context context, AttributeSet attrs) {
this(context.getResources(), context.getTheme(), attrs);
}

public DecelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
//...
public float getInterpolation(float input) {
float result;
if (mFactor == 1.0f) {
result = (float)(1.0f - (1.0f - input) * (1.0f - input));
} else {
result = (float)(1.0f - Math.pow((1.0f - input), 2 * mFactor));
}
return result;
}
//...
}

abstract public class BaseInterpolator implements Interpolator {
//....
}

public interface Interpolator extends TimeInterpolator {
//...
}

可以看到虽然 LinearInterpolator 、 AccelerateDecelerateInterpolator 、 DecelerateInterpolator 没有直接实现 TimeInterpolator 但是都间接实现了 TimeInterpolator 这便和策略模式对应了起来。而 Context 的角色就是由 Animation 类来扮演的,在 Animation 类中有 setInterpolator 方法,而具体的客户端就是我们 Android 程序员了,我们在使用 View 动画时会去设置插值器,这便是 Android 源码中对于策略模式的典型使用。
1
2
3
public void setInterpolator(Interpolator i) {
mInterpolator = i;
}

总结

优点

  1. 结构清晰
  2. 扩展性高、维护性好
  3. 耦合性低

缺点

具体的策略类繁多