1 基本介绍
策略模式(Strategy Pattern)是一种 举动型 设计模式,它封装了一系列的算法,使这些算法可以相互更换,从而 让 算法的变化 独立于 使用算法的客户端。
2 案例
本案例实现了只必要 更换排序算法的对象 就可以 更换具体的排序算法 的 Sorter 类,并在 Client 中测试了它。
2.1 Sortable 接口
- public interface Sortable { // 排序接口,实现它之后就能对 int 数组进行排序
- void sort(int[] nums); // 对 int 数组进行升序排序
- }
复制代码 2.2 BubbleSort 类
- public class BubbleSort implements Sortable { // 冒泡排序
- @Override
- public void sort(int[] nums) {
- for (int i = nums.length - 1; i > 0; i--) {
- for (int j = 0; j < i; j++) {
- if (nums[j] > nums[j + 1]) {
- int temp = nums[j];
- nums[j] = nums[j + 1];
- nums[j + 1] = temp;
- }
- }
- }
- }
- }
复制代码 2.3 SelectionSort 类
- public class SelectionSort implements Sortable { // 选择排序
- @Override
- public void sort(int[] nums) {
- for (int right = nums.length - 1; right > 0; right--) {
- // 每次选择操作都在未排序区间内找出最大的元素,然后与已排序区间的最左边的元素进行交换
- int max = right;
- for (int i = 0; i < right; i++) {
- if (nums[max] < nums[i]) {
- max = i;
- }
- }
- int temp = nums[right];
- nums[right] = nums[max];
- nums[max] = temp;
- }
- }
- }
复制代码 2.4 Sorter 类
- import java.util.Arrays;
- public class Sorter { // 执行排序操作,并打印排序结果的类
- private Sortable sortable;
-
- public Sorter(Sortable sortable) {
- this.sortable = sortable;
- }
- public void sort(int[] nums) {
- sortable.sort(nums);
- System.out.println(Arrays.toString(nums));
- }
- }
复制代码 2.5 Client 类
- import java.util.Random;
- public class Client { // 客户端,测试了 Sorter 的 sort()
- public static void main(String[] args) {
- final int LEN = 10;
- Sorter bubbleSorter = new Sorter(new BubbleSort());
- bubbleSorter.sort(generateRandomArray(LEN));
- System.out.println("=======================================");
- Sorter selectionSorter = new Sorter(new SelectionSort());
- selectionSorter.sort(generateRandomArray(LEN));
- }
- // 生成随机的 int 数组,长度为 length,用于测试排序
- private static int[] generateRandomArray(int length) {
- int[] arr = new int[length];
- Random random = new Random();
- for (int i = 0; i < length; i++) {
- arr[i] = random.nextInt(100);
- }
- return arr;
- }
- }
复制代码 2.6 Client 类的运行效果
- [0, 16, 20, 27, 29, 32, 34, 47, 55, 62]
- =======================================
- [17, 28, 40, 42, 42, 50, 50, 78, 80, 84]
复制代码 2.7 总结
通过 策略模式,将各个排序算法封装到实现某个 接口 的差异类的 sort() 方法中,从而能够通过 新增一个类 并 实现 sort() 方法 来提供新的排序算法,而不必要通过多个 if-else_if-else 结构来区分使用的是哪个排序算法,遵守了 开闭原则,加强了系统的 机动性。
3 各角色之间的关系
3.1 角色
3.1.1 Strategy ( 策略 )
该角色负责 定义 策略所必须的 接口。本案例中,Sortable 接口饰演了该角色。
3.1.2 ConcreteStrategy ( 具体的策略 )
该角色负责 实现 Strategy 角色中定义的 接口。本案例中,BubbleSort, SelectionSort 类都在饰演该角色。
3.1.3 Context ( 上下文 )
该角色负责 使用 Strategy 角色的方法实现需求。现实上,此处使用的是 Context 角色内部生存的 ConcreteStrategy 角色的方法。本案例中,Sorter 类饰演了该角色。
3.1.4 Client ( 客户端 )
该角色负责 给 Context 角色中传入 ConcreteStrategy 角色,并 使用 Context 角色完成具体的业务逻辑。本案例中,Client 类饰演了该角色。
3.2 类图
4 留意事项
- 分析项目中变化部分与不变部分:策略模式的关键在于分析项目中哪些部分是变化的,哪些部分是不变的。通常,算法或举动是变化的部分,而 使用这些算法或举动的上下文(Context)是不变的部分。通太过离变化部分和不变部分,可以使系统更加机动和可扩展。
- 多用组合/聚合,少用继承:策略模式的核心思想之一是 多用组合/聚合,少用继承。这意味着应该通过举动的 关联(组合/聚合 统称为 关联)而不是举动的 继承 来构建系统。通过使用 策略接口 和 具体的策略类,可以将算法封装在独立的类中,并通过 关联 的方式将它们与 上下文类 相关联。如许做可以使系统结构更加清晰,同时也更容易维护和扩展。
- 考虑混淆使用设计模式:当具体策略数目超过一定限度(如 6 个)时,可能必要考虑使用混淆别的设计模式来办理 策略类膨胀 和 对外暴露 的问题。通过联合其他设计模式(如 工厂模式)来优化策略模式的使用,使系统更加易于维护和管理。
5 在源码中的使用
在 JDK 中,Arrays 类的 sort() 就使用了 策略模式 的思想,角色如下:
- Strategy 角色:Comparator 接口中定义了 compare() 方法,这就是 策略方法。
- public interface Comparator<T> {
- int compare(T o1, T o2); // 比较 o1 和 o2 的大小
- // 剩余的方法与 比较 无关
- }
复制代码 - ConcreteStrategy 角色:自定义的实现了 Comparator 接口的内部类,如下所示:
- Comparator<Integer> integerComparator = new Comparator<Integer>() {
- @Override
- public int compare(Integer o1, Integer o2) {
- if (o1 > o2) {
- return 1;
- } else if (o1 < o2) {
- return -1;
- }
- return 0;
- }
- };
复制代码 - Context 角色:Arrays.sort() 方法就是上下文角色,使用具体的策略 c 完成排序的需求。
- public static <T> void sort(T[] a, Comparator<? super T> c) {
- if (c == null) {
- sort(a);
- } else {
- if (LegacyMergeSort.userRequested)
- legacyMergeSort(a, c); // 传统的归并排序,着重介绍本方法使用具体策略——c 的地方
- else
- // 这个排序的代码就不展示了,也使用了具体的策略——c
- TimSort.sort(a, 0, a.length, c, null, 0, 0);
- }
- }
- private static <T> void legacyMergeSort(T[] a, Comparator<? super T> c) {
- T[] aux = a.clone();
- if (c==null)
- mergeSort(aux, a, 0, a.length, 0);
- else
- mergeSort(aux, a, 0, a.length, 0, c);
- }
- private static void mergeSort(Object[] src, Object[] dest,
- int low, int high, int off, Comparator c) {
- int length = high - low;
- if (length < INSERTIONSORT_THRESHOLD) {
- for (int i=low; i<high; i++)
- // 以下代码中使用到了具体策略——c 的 compare() 方法
- for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
- swap(dest, j, j-1);
- return;
- }
- int destLow = low;
- int destHigh = high;
- low += off;
- high += off;
- int mid = (low + high) >>> 1;
- mergeSort(dest, src, low, mid, -off, c);
- mergeSort(dest, src, mid, high, -off, c);
- // 以下代码中使用到了具体策略——c 的 compare() 方法
- if (c.compare(src[mid-1], src[mid]) <= 0) {
- System.arraycopy(src, low, dest, destLow, length);
- return;
- }
- for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
- // 以下代码中使用到了具体策略——c 的 compare() 方法
- if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0)
- dest[i] = src[p++];
- else
- dest[i] = src[q++];
- }
- }
复制代码 6 优缺点
优点:
- 提高机动性:策略模式封装了一系列的算法,使它们可以 相互更换,使得 算法的变化 独立于 使用算法的客户,从而提高了系统的 机动性。
- 避免使用多重条件语句:如果一个类使用了多种算法,而这些算法的举动会根据多个条件来切换,那么这些条件判断通常会放在该类的方法中,以 多重条件语句 的形式出现。这会使该类变得 复杂 且 难以维护。策略模式通过定义策略接口和具体的策略类,将算法与类分离,避免了多重条件语句的使用。
- 遵循开闭原则:策略模式很好地遵循了 开闭原则,即对扩睁开放,对修改关闭。当必要增长新的算法时,只必要添加一个新的策略类,而不必要修改现有的上下文类或策略接口。从而提高了系统的 可扩展性。
- 简化单位测试:由于具体的策略类是独立的,而且封装了算法,因此可以很方便地对它们进行单位测试,而不必要涉及其他部分的代码。
缺点:
- 增长对象的数目:每增长一个策略,就必要增长一个具体策略类,这可能会导致系统中 类的数目增多,从而增长了系统的 复杂性。然而,这个缺点在大多数情况下是可以担当的,因为增长的类数目通常是有限的,而且这些类之间的关系非常清晰。
- 客户端必须了解全部的策略:在客户端代码中,通常必要指定使用哪一个策略。这要求客户端必须 了解全部的策略类,并能够在运行时选择符合的策略。这可能会使客户端代码的编写变得复杂,因为编写者必要知道许多策略类的细节。
- 策略切换的开销:在策略模式中,如果策略类中包罗了 大量的 状态 或 数据,那么在策略切换时,可能会涉及到这些状态或数据的 迁移。这可能会增长策略切换的开销,特别是在性能敏感的应用中。然而,这个问题通常可以通过 设计良好的 策略接口 和 上下文类 来避免。比方,在策略接口中只定义与算法相关的方法,将 状态 或 数据 生存在上下文类中。
7 适用场景
- 算法或举动多样且可更换:当一个系统必要 实现多种算法或举动,而且 这些算法或举动在运行时可以根据必要相互更换 时,可以使用策略模式。如许,客户端可以根据差异的情况选择差异的策略来执行,提高了系统的机动性和可扩展性。比方 排序算法、付出方式、游戏中仇人的攻击策略。
- 复杂的条件判断:如果 一个类中存在多个复杂的条件判断语句,用于选择差异的算法或举动,这会使类的代码变得难以理解和维护。此时,可以 将这些条件判断语句中的 每个分支 移入 各自的策略类 中,使每个策略类都封装了一个具体的算法或举动。如许,客户端只必要根据条件选择符合的策略类即可,无需关注复杂的条件判断逻辑。
- 必要隐蔽算法细节:在某些情况下,算法的实现细节对于客户端来说是透明的,客户端只必要知道算法的功能和如何使用它,而不必要知道算法的具体实现。策略模式可以将算法的实现封装在策略类中,并通过接口或抽象类向客户端提供同一的调用方式,从而隐蔽了算法的实现细节。
8 总结
策略模式 是一种 举动型 设计模式,它 让 算法的变化 独立于 使用算法的客户端,通过面向对象中的 多态 来更换 多重条件判断语句,使程序的编写变得更加简单,提高了系统的机动性和可扩展性。带来这些优点的同时,也具备着一些缺点,尤其是增长了程序中类的数目,使得系统更加复杂。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |