策略 Strategy

策略模式是一种行为设计模式,它能让你定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换。


为什么要使用?

策略模式的对象职责:

定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换。

👉 为了提高代码的可维护性。

👉 为了动态快速地替换更多的算法。

👉 为了应对需要频繁更换策略的场景。

💡 策略模式对算法起到了很好的封装作用,对于一些老旧的算法可以很方便地进行替换和升级。


模式结构

  • 上下文(Context)维护指向具体策略的引用,且仅通过策略接口与该对象进行交流。当上下文需要运行算法时,它会在其已连接的策略对象上调用执行方法。上下文不清楚其所涉及的策略类型与算法的执行方式。
  • 策略(Strategy)接口是所有具体策略的通用接口,它声明了一个上下文用于执行策略的方法。
  • 具体策略(Concrete Strategies)实现了上下文所用算法的各种不同变体。
  • 客户端(Client)会创建一个特定策略对象并将其传递给上下文。上下文则会提供一个设置器以便客户端在运行时替换相关联的策略。

策略模式的类图:


模式实现

该示例使用策略模式将不同的排序算法(冒泡排序和插入排序)变为一个个单独的类。通过在 IntTypeSort 类中注入不同的策略,来调用不同的排序算法。

示例程序的类图

代码实现

上下文
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
package example;

/** int类型排序的上下文 */
public class IntTypeSort {
/** 排序策略 */
private ISort sortStrategy;

public IntTypeSort(ISort sortStrategy) {
this.sortStrategy = sortStrategy;
}

/**
* 从小到大顺序排序
* @param arrays 待排序数组
* @return 排序好的数组
*/
public int[] getOrder(int[] arrays) {
return sortStrategy.sort(arrays, true);
}

/**
* 从大到小逆序排序
* @param arrays 待排序数组
* @return 排序好的数组
*/
public int[] getReverseOrder(int[] arrays) {
return sortStrategy.sort(arrays, false);
}
}
策略接口
1
2
3
4
5
6
7
8
9
10
11
12
package example;

/** 排序策略接口 */
public interface ISort {
/**
* 排序算法
* @param arrays 待排序数组
* @param flag true从小到大顺序排序,false从大到小逆序排序
* @return 排序好的数组
*/
int[] sort(int[] arrays, boolean flag);
}
具体策略
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
package example;

/** 冒泡排序策略 */
public class BubbleSort implements ISort {
/**
* 排序算法
* @param arrays 待排序数组
* @param flag true从小到大顺序排序,false从大到小逆序排序
* @return 排序好的数组
*/
@Override
public int[] sort(int[] arrays, boolean flag) {
if (arrays.length == 0) {
return arrays;
}
for (int i = 0; i < arrays.length; i++)
{
for (int j = 0; j < arrays.length - 1 - i; j++)
{
if (arrays[j + 1] < arrays[j]) {
int temp = arrays[j + 1];
arrays[j + 1] = arrays[j];
arrays[j] = temp;
}
}
}
// 从大到小排序
if (!flag) {
int len = arrays.length;
int[] temp = new int[len];
for (int i = 0; i < len; i++) {
temp[i] = arrays[len - i - 1];
}
return temp;
}
return arrays;
}
}
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
package example;

/** 插入排序策略 */
public class InsertionSort implements ISort {
/**
* 排序算法
* @param arrays 待排序数组
* @param flag true从小到大顺序排序,false从大到小逆序排序
* @return 排序好的数组
*/
@Override
public int[] sort(int[] arrays, boolean flag) {
if (arrays.length == 0)
{
return arrays;
}
int current;
for (int i = 0; i < arrays.length - 1; i++) {
current = arrays[i + 1];
int preIndex = i;
while (preIndex >= 0 && current < arrays[preIndex]) {
arrays[preIndex + 1] = arrays[preIndex];
preIndex--;
}
arrays[preIndex + 1] = current;
}
// 从大到小排序
if (!flag) {
int len = arrays.length;
int[] temp = new int[len];
for (int i = 0; i < len; i++) {
temp[i] = arrays[len - i - 1];
}
return temp;
}
return arrays;
}
}

代码测试

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
import example.IntTypeSort;
import example.BubbleSort;
import example.InsertionSort;

import java.util.Arrays;

/** 测试策略模式 */
public class Test {
public static void main(String[] args) {
int[] arrays = new int[]{6, 1, 4, 9, 2, 3, 7, 5, 8};
System.out.print("原始数组为:");
Arrays.stream(arrays).forEach(item -> System.out.print(item + " "));
System.out.println("\n");

IntTypeSort bubbleSort = new IntTypeSort(new BubbleSort());
IntTypeSort insertionSort = new IntTypeSort(new InsertionSort());

System.out.println("冒泡排序策略:");
System.out.print("从小到大顺序排序:");
Arrays.stream(bubbleSort.getOrder(arrays)).forEach(item -> System.out.print(item + " "));
System.out.println();
System.out.print("从大到小逆序排序:");
Arrays.stream(bubbleSort.getReverseOrder(arrays)).forEach(item -> System.out.print(item + " "));
System.out.println();

System.out.println("\n----------------------- 分割线 -----------------------\n");

System.out.println("插入排序策略:");
System.out.print("从小到大顺序排序:");
Arrays.stream(insertionSort.getOrder(arrays)).forEach(item -> System.out.print(item + " "));
System.out.println();
System.out.print("从大到小逆序排序:");
Arrays.stream(insertionSort.getReverseOrder(arrays)).forEach(item -> System.out.print(item + " "));
System.out.println();
}
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
原始数组为:6 1 4 9 2 3 7 5 8 

冒泡排序策略:
从小到大顺序排序:1 2 3 4 5 6 7 8 9
从大到小逆序排序:9 8 7 6 5 4 3 2 1

----------------------- 分割线 -----------------------

插入排序策略:
从小到大顺序排序:1 2 3 4 5 6 7 8 9
从大到小逆序排序:9 8 7 6 5 4 3 2 1

使用 IntTypeSort bubbleSort = new IntTypeSort(new BubbleSort());IntTypeSort insertionSort = new IntTypeSort(new InsertionSort()); 注入不同策略,再根据不同策略执行排序。


常用场景和解决方案

  • 系统中需要动态切换几种算法的场景。
  • 使用多重的条件选择语句来实现的业务场景。例如,当类中使用了复杂条件运算符以在同一算法的不同变体中切换时。
  • 只希望客户端选择已经封装好的算法场景而不关心算法实现细节。
  • 如果算法在上下文的逻辑不是非常重要,策略模式可以让你分离使用策略和创建策略的场景。
  • 当你有许多仅在执行某些行为时略有不同的相似类时,可使用策略模式。策略模式让你能将不同行为抽取到一个独立类层次结构中,并将原始类组合成同一个,从而减少重复代码。

模式的优缺点

优点 缺点
你可以在运行时切换对象内的算法,提升代码灵活性。 如果你的算法极少发生改变,那么没有任何理由引入新的类和接口。使用该模式只会让程序过于复杂。
你可以将算法的实现和使用算法的代码隔离开来。 客户端必须知晓策略间的不同——它需要选择合适的策略。客户端的学习成本变高。
提供了一种管理多个不同算法策略的方法,能够降低使用多重 if-else 嵌套语句的理解难度。 许多现代编程语言支持函数类型功能,允许你在一组匿名函数中实现不同版本的算法。这样,你使用这些函数的方式就和使用策略对象时完全相同,无需借助额外的类和接口来保持代码简洁。
开闭原则。你无需对上下文进行修改就能够引入新的策略,提供良好的代码扩展性。 具体策略类的数量会剧增,增加维护成本。
你可以使用组合来代替继承。

拓展知识

  • 装饰模式可让你更改对象的外表,策略则让你能够改变其本质。
  • 桥接模式、状态模式和策略模式(在某种程度上包括适配器模式)模式的接口非常相似。实际上,它们都基于组合模式——即将工作委派给其他对象,不过也各自解决了不同的问题。模式并不只是以特定方式组织代码的配方,你还可以使用它们来和其他开发者讨论模式所解决的问题。
  • 模板方法模式基于继承机制:它允许你通过扩展子类中的部分内容来改变部分算法。策略基于组合机制:你可以通过对相应行为提供不同的策略来改变对象的部分行为。模板方法在类层次上运作,因此它是静态的。策略在对象层次上运作,因此允许在运行时切换行为。


🔙 设计模式

📌最后:希望本文能够给您提供帮助,文章中有不懂或不正确的地方,请在下方评论区💬留言!

🔗参考文献:

🌐 设计模式 –refactoringguru

▶️ bilibili-趣学设计模式;黄靖锋. –拉勾教育

📖 图解设计模式 /(日)结城浩著;杨文轩译. –北京:人民邮电出版社,2017.1