十六、策略模式

打印 上一主题 下一主题

主题 574|帖子 574|积分 1722


1 基本介绍

策略模式(Strategy Pattern)是一种 举动型 设计模式,它封装了一系列的算法,使这些算法可以相互更换,从而 让 算法的变化 独立于 使用算法的客户端
2 案例

本案例实现了只必要 更换排序算法的对象 就可以 更换具体的排序算法 的 Sorter 类,并在 Client 中测试了它。
2.1 Sortable 接口

  1. public interface Sortable { // 排序接口,实现它之后就能对 int 数组进行排序
  2.     void sort(int[] nums); // 对 int 数组进行升序排序
  3. }
复制代码
2.2 BubbleSort 类

  1. public class BubbleSort implements Sortable { // 冒泡排序
  2.     @Override
  3.     public void sort(int[] nums) {
  4.         for (int i = nums.length - 1; i > 0; i--) {
  5.             for (int j = 0; j < i; j++) {
  6.                 if (nums[j] > nums[j + 1]) {
  7.                     int temp = nums[j];
  8.                     nums[j] = nums[j + 1];
  9.                     nums[j + 1] = temp;
  10.                 }
  11.             }
  12.         }
  13.     }
  14. }
复制代码
2.3 SelectionSort 类

  1. public class SelectionSort implements Sortable { // 选择排序
  2.     @Override
  3.     public void sort(int[] nums) {
  4.         for (int right = nums.length - 1; right > 0; right--) {
  5.             // 每次选择操作都在未排序区间内找出最大的元素,然后与已排序区间的最左边的元素进行交换
  6.             int max = right;
  7.             for (int i = 0; i < right; i++) {
  8.                 if (nums[max] < nums[i]) {
  9.                     max = i;
  10.                 }
  11.             }
  12.             int temp = nums[right];
  13.             nums[right] = nums[max];
  14.             nums[max] = temp;
  15.         }
  16.     }
  17. }
复制代码
2.4 Sorter 类

  1. import java.util.Arrays;
  2. public class Sorter { // 执行排序操作,并打印排序结果的类
  3.     private Sortable sortable;
  4.    
  5.     public Sorter(Sortable sortable) {
  6.         this.sortable = sortable;
  7.     }
  8.     public void sort(int[] nums) {
  9.         sortable.sort(nums);
  10.         System.out.println(Arrays.toString(nums));
  11.     }
  12. }
复制代码
2.5 Client 类

  1. import java.util.Random;
  2. public class Client { // 客户端,测试了 Sorter 的 sort()
  3.     public static void main(String[] args) {
  4.         final int LEN = 10;
  5.         Sorter bubbleSorter = new Sorter(new BubbleSort());
  6.         bubbleSorter.sort(generateRandomArray(LEN));
  7.         System.out.println("=======================================");
  8.         Sorter selectionSorter = new Sorter(new SelectionSort());
  9.         selectionSorter.sort(generateRandomArray(LEN));
  10.     }
  11.     // 生成随机的 int 数组,长度为 length,用于测试排序
  12.     private static int[] generateRandomArray(int length) {
  13.         int[] arr = new int[length];
  14.         Random random = new Random();
  15.         for (int i = 0; i < length; i++) {
  16.             arr[i] = random.nextInt(100);
  17.         }
  18.         return arr;
  19.     }
  20. }
复制代码
2.6 Client 类的运行效果

  1. [0, 16, 20, 27, 29, 32, 34, 47, 55, 62]
  2. =======================================
  3. [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() 方法,这就是 策略方法
    1. public interface Comparator<T> {
    2.         int compare(T o1, T o2); // 比较 o1 和 o2 的大小
    3.         // 剩余的方法与 比较 无关
    4. }
    复制代码
  • ConcreteStrategy 角色:自定义的实现了 Comparator 接口的内部类,如下所示:
    1. Comparator<Integer> integerComparator = new Comparator<Integer>() {
    2.        @Override
    3.        public int compare(Integer o1, Integer o2) {
    4.             if (o1 > o2) {
    5.                 return 1;
    6.             } else if (o1 < o2) {
    7.                 return -1;
    8.             }
    9.             return 0;
    10.        }
    11. };
    复制代码
  • Context 角色:Arrays.sort() 方法就是上下文角色,使用具体的策略 c 完成排序的需求。
    1. public static <T> void sort(T[] a, Comparator<? super T> c) {
    2.     if (c == null) {
    3.         sort(a);
    4.     } else {
    5.         if (LegacyMergeSort.userRequested)
    6.             legacyMergeSort(a, c); // 传统的归并排序,着重介绍本方法使用具体策略——c 的地方
    7.         else
    8.                 // 这个排序的代码就不展示了,也使用了具体的策略——c
    9.             TimSort.sort(a, 0, a.length, c, null, 0, 0);
    10.     }
    11. }
    12. private static <T> void legacyMergeSort(T[] a, Comparator<? super T> c) {
    13.     T[] aux = a.clone();
    14.     if (c==null)
    15.         mergeSort(aux, a, 0, a.length, 0);
    16.     else
    17.         mergeSort(aux, a, 0, a.length, 0, c);
    18. }
    19. private static void mergeSort(Object[] src, Object[] dest,
    20.                               int low, int high, int off, Comparator c) {
    21.     int length = high - low;
    22.     if (length < INSERTIONSORT_THRESHOLD) {
    23.         for (int i=low; i<high; i++)
    24.                 // 以下代码中使用到了具体策略——c 的 compare() 方法
    25.             for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
    26.                 swap(dest, j, j-1);
    27.         return;
    28.     }
    29.     int destLow  = low;
    30.     int destHigh = high;
    31.     low  += off;
    32.     high += off;
    33.     int mid = (low + high) >>> 1;
    34.     mergeSort(dest, src, low, mid, -off, c);
    35.     mergeSort(dest, src, mid, high, -off, c);
    36.     // 以下代码中使用到了具体策略——c 的 compare() 方法
    37.     if (c.compare(src[mid-1], src[mid]) <= 0) {
    38.        System.arraycopy(src, low, dest, destLow, length);
    39.        return;
    40.     }
    41.     for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
    42.         // 以下代码中使用到了具体策略——c 的 compare() 方法
    43.         if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0)
    44.             dest[i] = src[p++];
    45.         else
    46.             dest[i] = src[q++];
    47.     }
    48. }
    复制代码
6 优缺点

优点


  • 提高机动性:策略模式封装了一系列的算法,使它们可以 相互更换使得 算法的变化 独立于 使用算法的客户,从而提高了系统的 机动性
  • 避免使用多重条件语句:如果一个类使用了多种算法,而这些算法的举动会根据多个条件来切换,那么这些条件判断通常会放在该类的方法中,以 多重条件语句 的形式出现。这会使该类变得 复杂难以维护。策略模式通过定义策略接口和具体的策略类,将算法与类分离,避免了多重条件语句的使用。
  • 遵循开闭原则:策略模式很好地遵循了 开闭原则,即对扩睁开放,对修改关闭。当必要增长新的算法时,只必要添加一个新的策略类,而不必要修改现有的上下文类或策略接口。从而提高了系统的 可扩展性
  • 简化单位测试:由于具体的策略类是独立的,而且封装了算法,因此可以很方便地对它们进行单位测试,而不必要涉及其他部分的代码。
缺点


  • 增长对象的数目:每增长一个策略,就必要增长一个具体策略类,这可能会导致系统中 类的数目增多,从而增长了系统的 复杂性。然而,这个缺点在大多数情况下是可以担当的,因为增长的类数目通常是有限的,而且这些类之间的关系非常清晰。
  • 客户端必须了解全部的策略:在客户端代码中,通常必要指定使用哪一个策略。这要求客户端必须 了解全部的策略类,并能够在运行时选择符合的策略。这可能会使客户端代码的编写变得复杂,因为编写者必要知道许多策略类的细节。
  • 策略切换的开销:在策略模式中,如果策略类中包罗了 大量的 状态 或 数据,那么在策略切换时,可能会涉及到这些状态或数据的 迁移。这可能会增长策略切换的开销,特别是在性能敏感的应用中。然而,这个问题通常可以通过 设计良好的 策略接口 和 上下文类 来避免。比方,在策略接口中只定义与算法相关的方法将 状态 或 数据 生存在上下文类中
7 适用场景



  • 算法或举动多样且可更换:当一个系统必要 实现多种算法或举动,而且 这些算法或举动在运行时可以根据必要相互更换 时,可以使用策略模式。如许,客户端可以根据差异的情况选择差异的策略来执行,提高了系统的机动性和可扩展性。比方 排序算法付出方式游戏中仇人的攻击策略
  • 复杂的条件判断:如果 一个类中存在多个复杂的条件判断语句用于选择差异的算法或举动,这会使类的代码变得难以理解和维护。此时,可以 将这些条件判断语句中的 每个分支 移入 各自的策略类 中,使每个策略类都封装了一个具体的算法或举动。如许,客户端只必要根据条件选择符合的策略类即可,无需关注复杂的条件判断逻辑。
  • 必要隐蔽算法细节:在某些情况下,算法的实现细节对于客户端来说是透明的,客户端只必要知道算法的功能和如何使用它,而不必要知道算法的具体实现。策略模式可以将算法的实现封装在策略类中,并通过接口或抽象类向客户端提供同一的调用方式,从而隐蔽了算法的实现细节。
8 总结

策略模式 是一种 举动型 设计模式,它 让 算法的变化 独立于 使用算法的客户端,通过面向对象中的 多态 来更换 多重条件判断语句,使程序的编写变得更加简单,提高了系统的机动性和可扩展性。带来这些优点的同时,也具备着一些缺点,尤其是增长了程序中类的数目,使得系统更加复杂。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

笑看天下无敌手

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表