0%

设计模式之Strategy模式

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改.

前言

策略模式比较简单,其实就是使用抽象类或者接口,定义一个方法,然后子类来实现相应的方法,之后通过一个新的类来使用这个已经定义好的组件,使用委托(组合)的方式,让定义的接口根据多态来灵活的使用相应的实现方法,最终完成一定的功能.和Builder模式比起来,Builder中新建的类还要对接口中的元素方法进行组合和操作,而策略模式只用一个方法就可以,没有这种复杂的操作,就是简单的使用已经定义好的方法,不用再次封装操作.和桥接模式相比,差别也是很明显的,桥接是功能层次和实现层次分离,而策略模式是方便扩充策略,但是这几种模式的某些部分是很类似的,只不过完成的任务有些许不同而已.

实现

  • 示例1:

    Sorter.java
    1
    2
    3
    4
    5
    package com.edu.tju.GOF.Strategy.Sample1;

    public interface Sorter {
    public abstract void sort(Comparable []data);
    }
    BubbleSorter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.edu.tju.GOF.Strategy.Sample1;

public class BubbleSorter implements Sorter {

public void sort(Comparable[] data) {
for (int i = 0; i < data.length; i++) {
for (int j = i + 1; j < data.length; j++) {
if (data[i].compareTo(data[j]) > 0) {
Comparable temp = data[i];
data[i] = data[j];
data[j] = temp;
}
}
}
}
}

QuickSorter.java

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
package com.edu.tju.GOF.Strategy.Sample1;

public class QuickSorter implements Sorter {

public void sort(Comparable[] data) {
int low = 0, high = data.length - 1;
Qsort(data, low, high);
}

/************** 快速排序 *****************/
private int Partiton(Comparable[] data, int low, int high) {
Comparable temp, pivotkey;
pivotkey = data[low]; // 选取支点
temp = pivotkey; // 暂存支点
while (low < high) // 条件,相等则结束
{
while (low < high && data[high].compareTo(pivotkey) >= 0) {
high--;// 左移
}
data[low] = data[high];// 覆盖
while (low < high && data[low].compareTo(pivotkey) <= 0) {
low++; // 右移
}
data[high] = data[low];// 覆盖
}
data[low] = temp; // 恢复
return low; // 返回分界点位置
}

private void Qsort(Comparable[] data, int low, int high) {
int pivot;
if (low < high) {
pivot = Partiton(data, low, high); // 获得分界点位置
Qsort(data, low, pivot - 1); // 排左边
Qsort(data, pivot + 1, high);// 右边
}
}

}

SortAndPrint.java

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
package com.edu.tju.GOF.Strategy.Sample1;

public class SortAndPrint {
private Comparable[] data;
private Sorter sorter;

public SortAndPrint(Comparable[] data, Sorter sorter) {
this.data = data;
this.sorter = sorter;
}

public void execuate() {
printResult();
sorter.sort(data);
printResult();
}

public void printResult() {
System.out.println("=========================");
for (int i = 0; i < data.length; i++) {
System.out.print(data[i] + " ");
}
System.out.println("\n=========================");
}
}

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.edu.tju.GOF.Strategy.Sample1;

public class Main {

public static void main(String[] args) {

Comparable[] data1 = { "rlj", "bb", "love", "forever" };
SortAndPrint sap1 = new SortAndPrint(data1, new QuickSorter());
System.out.println("=====策略一======");
sap1.execuate();
System.out.println("=====策略二======");
Comparable[] data2 = { "rlj", "jx", "love", "forever" };
SortAndPrint sap2 = new SortAndPrint(data2, new BubbleSorter());
sap2.execuate();

}

}
  • 示例2:

创建一个接口。
Strategy.java

1
2
3
public interface Strategy {
public int doOperation(int num1, int num2);
}

创建实现接口的实体类。
OperationAdd.java

1
2
3
4
5
6
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}

OperationSubstract.java

1
2
3
4
5
6
public class OperationSubstract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}

OperationMultiply.java

1
2
3
4
5
6
public class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}

创建 Context 类。
Context.java

1
2
3
4
5
6
7
8
9
10
11
public class Context {
private Strategy strategy;

public Context(Strategy strategy){
this.strategy = strategy;
}

public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}

使用 Context 来查看当它改变策略 Strategy 时的行为变化。
StrategyPatternDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

context = new Context(new OperationSubstract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}

Strategy 模式中的登场角色

  • 策略 (Strategy) 接口是所有具体策略的通用接口, 它声明了一个上下文用于执行策略的方法.

  • 具体策略 (Concrete Strategies) 实现了上下文所用算法的各种不同变体.

  • 上下文 (Context) 维护指向具体策略的引用, 且仅通过策略接口与该对象进行交流.

  • 类图

    策略模式应用场景

  • 当你想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式.
    策略模式让你能够将对象关联至可以不同方式执行特定子任务的不同子对象, 从而以间接方式在运行时更改对象行为.

  • 当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式.
    策略模式让你能将不同行为抽取到一个独立类层次结构中, 并将原始类组合成同一个, 从而减少重复代码.

  • 如果算法在逻辑的上下文中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来.
    策略模式让你能将各种算法的代码、 内部数据和依赖关系与其他代码隔离开来. 不同客户端可通过一个简单接口执行算法, 并能在运行时进行切换.

  • 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式.
    策略模式将所有继承自同样接口的算法抽取到独立类中, 因此不再需要条件语句. 原始对象并不实现所有算法的变体, 而是将执行工作委派给其中的一个独立算法对象.

    实现方式

  • 从上下文类中找出修改频率较高的算法 (也可能是用于在运行时选择某个算法变体的复杂条件运算符).

  • 声明该算法所有变体的通用策略接口.

  • 将算法逐一抽取到各自的类中, 它们都必须实现策略接口.

  • 在上下文类中添加一个成员变量用于保存对于策略对象的引用. 然后提供设置器以修改该成员变量. 上下文仅可通过策略接口同策略对象进行交互, 如有需要还可定义一个接口来让策略访问其数据.

  • 客户端必须将上下文类与相应策略进行关联, 使上下文可以预期的方式完成其主要工作

优缺点

  • 策略模式优点

    • 你可以在运行时切换对象内的算法.
    • 你可以将算法的实现和使用算法的代码隔离开来.
    • 你可以使用组合来代替继承.
    • 开闭原则. 你无需对上下文进行修改就能够引入新的策略.
  • 策略模式缺点

    • 如果你的算法极少发生改变, 那么没有任何理由引入新的类和接口. 使用该模式只会让程序过于复杂.
    • 客户端必须知晓策略间的不同——它需要选择合适的策略.
    • 许多现代编程语言支持函数类型功能, 允许你在一组匿名函数中实现不同版本的算法. 这样, 你使用这些函数的方式就和使用策略对象时完全相同, 无需借助额外的类和接口来保持代码简洁.

      为什么需要特意编写Strategy角色呢

      通常在编程时,算法会被卸载具体方法中.而Strategy模式却特意将算法与其他部分分离开来,只是定义了与该算法相关的接口(API),然后在程序中以委托的方法来使用算法.
      这样看来,程序好像变复杂了.其实不然,例如,当我们想要通过改善算法来提高算法的处理速度时,如果使用了Strategy模式,就不必修改Stragegy角色的接口(API)了,仅仅修稿ConcreteStrategy角色即可.而且使用委托这种弱关联关系而已很方便地整体替换算法,例如,如果相比较原来的算法与改进后的算法的处理速度有多大区别,简单地替换下算法即可进行测试.
      使用Stragegy模式编写想起程序时,可以方便地根据棋手的选择切换AI例程的水平.

与其他模式的关系

  • 装饰者模式可让你更改对象的外表, 策略则让你能够改变其本质.
  • 模板方法模式基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法. 策略基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为. 模板方法在类层次上运作, 因此它是静态的. 策略在对象层次上运作, 因此允许在运行时切换行为.
Thank you for your support