文章目录
- 1 基本介绍
- 2 案例
- 2.1 Sortable 接口
- 2.2 BubbleSort 类
- 2.3 SelectionSort 类
- 2.4 Sorter 类
- 2.5 Client 类
- 2.6 Client 类的运行结果
- 2.7 总结
- 3 各角色之间的关系
- 3.1 角色
- 3.1.1 Strategy ( 策略 )
- 3.1.2 ConcreteStrategy ( 具体的策略 )
- 3.1.3 Context ( 上下文 )
- 3.1.4 Client ( 客户端 )
- 3.2 类图
- 4 注意事项
- 5 在源码中的使用
- 6 优缺点
- 7 适用场景
- 8 总结
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 { // 冒泡排序@Overridepublic 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 { // 选择排序@Overridepublic 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>() {@Overridepublic 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// 这个排序的代码就不展示了,也使用了具体的策略——cTimSort.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);elsemergeSort(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++];elsedest[i] = src[q++];} }
6 优缺点
优点:
- 提高灵活性:策略模式封装了一系列的算法,使它们可以 相互替换,使得 算法的变化 独立于 使用算法的客户,从而提高了系统的 灵活性。
- 避免使用多重条件语句:如果一个类使用了多种算法,而这些算法的行为会根据多个条件来切换,那么这些条件判断通常会放在该类的方法中,以 多重条件语句 的形式出现。这会使该类变得 复杂 且 难以维护。策略模式通过定义策略接口和具体的策略类,将算法与类分离,避免了多重条件语句的使用。
- 遵循开闭原则:策略模式很好地遵循了 开闭原则,即对扩展开放,对修改关闭。当需要增加新的算法时,只需要添加一个新的策略类,而不需要修改现有的上下文类或策略接口。从而提高了系统的 可扩展性。
- 简化单元测试:由于具体的策略类是独立的,并且封装了算法,因此可以很方便地对它们进行单元测试,而不需要涉及其他部分的代码。
缺点:
- 增加对象的数目:每增加一个策略,就需要增加一个具体策略类,这可能会导致系统中 类的数目增多,从而增加了系统的 复杂性。然而,这个缺点在大多数情况下是可以接受的,因为增加的类数量通常是有限的,并且这些类之间的关系非常清晰。
- 客户端必须了解所有的策略:在客户端代码中,通常需要指定使用哪一个策略。这要求客户端必须 了解所有的策略类,并能够在运行时选择合适的策略。这可能会使客户端代码的编写变得复杂,因为编写者需要知道很多策略类的细节。
- 策略切换的开销:在策略模式中,如果策略类中包含了 大量的 状态 或 数据,那么在策略切换时,可能会涉及到这些状态或数据的 迁移。这可能会增加策略切换的开销,特别是在性能敏感的应用中。然而,这个问题通常可以通过 设计良好的 策略接口 和 上下文类 来避免。例如,在策略接口中只定义与算法相关的方法,将 状态 或 数据 保存在上下文类中。
7 适用场景
- 算法或行为多样且可替换:当一个系统需要 实现多种算法或行为,并且 这些算法或行为在运行时可以根据需要相互替换 时,可以使用策略模式。这样,客户端可以根据不同的情况选择不同的策略来执行,提高了系统的灵活性和可扩展性。例如 排序算法、支付方式、游戏中敌人的攻击策略。
- 复杂的条件判断:如果 一个类中存在多个复杂的条件判断语句,用于选择不同的算法或行为,这会使类的代码变得难以理解和维护。此时,可以 将这些条件判断语句中的 每个分支 移入 各自的策略类 中,使每个策略类都封装了一个具体的算法或行为。这样,客户端只需要根据条件选择合适的策略类即可,无需关注复杂的条件判断逻辑。
- 需要隐藏算法细节:在某些情况下,算法的实现细节对于客户端来说是透明的,客户端只需要知道算法的功能和如何使用它,而不需要知道算法的具体实现。策略模式可以将算法的实现封装在策略类中,并通过接口或抽象类向客户端提供统一的调用方式,从而隐藏了算法的实现细节。
8 总结
策略模式 是一种 行为型 设计模式,它 让 算法的变化 独立于 使用算法的客户端,通过面向对象中的 多态 来替换 多重条件判断语句,使程序的编写变得更加简单,提高了系统的灵活性和可扩展性。带来这些优点的同时,也具备着一些缺点,尤其是增加了程序中类的数量,使得系统更加复杂。