策略 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;
public class IntTypeSort { private ISort sortStrategy;
public IntTypeSort(ISort sortStrategy) { this.sortStrategy = sortStrategy; }
public int[] getOrder(int[] arrays) { return sortStrategy.sort(arrays, true); }
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 {
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 {
@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 {
@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