目 录CONTENT

文章目录

策略模式

半糖
2024-08-31 / 0 评论 / 0 点赞 / 9 阅读 / 13840 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2024-08-31,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

名称

策略模式(STRATEGY),别名:政策(Policy)

目的

定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

适用性

当存在以下情况时使用Strategy模式:

  • 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。

  • 需要使用一个算法的不同变体。例如,会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使用策略模式。

  • 隐藏不应该让客户知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。

  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

结构

策略(Strategy):定义所有支持的算法的公共接口。Context使用这个接口来调用某ConcreteStrategy定义的算法。

具体策略(ConcreteStrategy):以Strategy接口实现某具体算法。

上下文(Context)

  • 用一个ConcreteStrategy对象来配置。

  • 维护一个对Strategy对象的引用。

  • 可定义一个接口来让Stategy访问它的数据。

协作

  1. Strategy和Context相互作用以实现选定的算法。当算法被调用时,Context可以将该算法所需要的所有数据都传递给该Stategy。或者,Context可以将自身作为一个参数传递给Strategy操作。这就让Strategy在需要时可以回调Context。

  2. Context将它的客户的请求转发给它的Strategy。客户通常创建并传递一个ConcreteStrategy对象给该Context;这样,客户仅与Context交互。通常有一系列的ConcreteStrategy类可供客户从中选择。

效果

优点

  1. Strategy类层次为Context定义了一系列的可供重用的算法或行为,继承有助于析取出这些算法中的公共功能。

  2. 作为一个替代继承的方法。继承提供了另一种支持多种算法或行为的方法,可以直接生成一个Context类的子类,从而给它以不同的行为。但这会将行为强行编织到Context中,导致算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且还不能动态地改变算法,最后得到一堆相关的类,它们唯一差别是它们所使用的算法或行为。将算法封装在独立的Strategy类的好处就是可以独立于其Context来改变它,使它易于切换、易于理解、易于扩展

  3. 消除了一些条件语句。Strategy模式提供了用条件语句选择所需的行为以外的另一种选择,当不同的行为堆砌在一个类中时,很难避免需要使用条件语句来选择合适的行为,将行为封装在一个个独立的Strategy类中消除了这些条件语句。含有许多条件语句的代码通常意味着需要使用Strategy模式

  4. 可以选择行为的具体实现。Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间/空间权衡取舍要求从不同策略中进行选择。

缺点

  1. 客户必须了解不同的Strategy。本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同,此时可能不得不向客户暴露具体的实现问题。

  2. Strategy和Context之间的通信开销。无论各个ConcreteStrategy实现的算法是简单还是复杂,它们都共享Strategy定义的接口。因此很可能某些ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。如果存在这样问题,那么将需要在Strategy和Context之间更进行紧密的耦合(比如直接context直接传具体参数给strategy,这样扩展性就不高了)。

  3. 增加了对象的数目。Strategy增加了一个应用中的对象的数目。有时可以将Strategy实现为可供各Context共享的无状态的对象来减少这一开销,任何其余的状态都由Context维护。Context在每一次对Strategy对象的请求中都将这个状态传递过去,共享的Stragey不应在各次调用之间维护状态,享元模式(Flyweight)更详细地描述了这一方法

实现

  1. 定义Strategy和Context接口。Strategy和Context接口必须使得ConcreteStrategy能够有效的访问它所需要的Context中的任何数据,反之亦然。有下列三种方式(代码示例中test1、test2、test3分别讲解):

    1. context通过参数传递数据给strategy。(推模型)

    2. context通过context传递数据给strategy。(拉模型)

    3. 无需传递数据,context通过ConcreteStrategy中的context引用传递数据给strategy,这样做的前提是ConcreteStrategy必须有context的对象引用,换句话说,也就是他们互相依赖了。(拉模型)

  2. 将Strategy作为模板参数。可利用模板机制用一个Strategy来配置一个类,然而这种技术仅当下面条件满足时才可以使用(1)可以在编译时选择Strategy(2)它不需在运行时改变。在这种情况下,要被配置的类(如Context)被定义为以一个Strategy类作为一个参数的模板类。使用模板不再需要定义给Strategy定义接口的抽象类,把Strategy作为一个模板参数也使得可以将一个Strategy和它的Context静态地绑定在一起,从而提高效率。下面是关于这句话的代码示例

    /** 
     * 策略接口
     */
    public interface Strategy<T> {
         T execute(T data);
    }
    /** 
     * 具体策略A
     */
    public class ConcreteStrategyA implements Strategy<Integer> {
         @Override
         public Integer execute(Integer data) {
             return data * 2; // 具体的策略实现
         }
     }
    /** 
     * 具体策略B
     */
    public class ConcreteStrategyB implements Strategy<Integer> {
         @Override
         public Integer execute(Integer data) {
             return data + 10; // 另一种策略实现
         }
     }
    /** 
     * 上下文
     */
    public class Context<T, S extends Strategy<T>> {
       private final S strategy;
    
       public Context(S strategy) {
           this.strategy = strategy;
       }
    
       public T execute(T data) {
           return strategy.execute(data);
       }
     }
    /** 
     * 上下文
     */
    public class Client{
      public static void main(String[] args) {
         // 使用策略A
         Strategy<Integer> strategyA = new ConcreteStrategyA();
         Context<Integer, Strategy<Integer>> contextA = new Context<>(strategyA);
         System.out.println("Result with Strategy A: " + contextA.execute(5)); // 输出 10
    
         // 使用策略B
         Strategy<Integer> strategyB = new ConcreteStrategyB();
         Context<Integer, Strategy<Integer>> contextB = new Context<>(strategyB);
         System.out.println("Result with Strategy B: " + contextB.execute(5)); // 输出 15
      }
    }
  3. 使Strategy对象成为可选项。如果即使在不使用额外的Strategy对象的情况下,Context也还有意义的话,那么它还可以被简化。Context在访问某Strategy前先检查它是否存在,如果有,那么就使用它;如果没有,那么Context执行缺省的行为。这种方法的好处是客户根本不需要处理Strategy对象,除非它们不喜欢缺省的行为。java中可以使用抽象类来定义缺省行为,这样具体策略相当于if,抽象类中的方法相当于else。

应用

问题

编写一个排序工具类,可以根据不同的策略实现排序(结构算法中排序的方式有很多,比如选择排序、冒泡排序、插入排序),为了实现能任意切换排序策略,我们使用策略模式对这一功能进行设计。首先定义一个排序策略为抽象策略(如果要定义缺省行为使用抽象类,这里是接口),然后定义选择排序策略、冒泡排序策略、插入排序策略为具体策略,为了能拉取到上下文的数据,我们在具体策略中保留一个上下文引用(有些方法用不着);最后定义上下文,结构很简单,一个策略引用,一个存储数据的数组,然后是三个方法分别调用策略的三个方法,根据传入的具体策略决定每个方法执行的具体策略方法。

示例

UML

代码示例

策略:排序策略,定义三个方法,用来演示“实现”小节的传递数据方式

package com.ysj.part5.strategy.strategy;

import com.ysj.part5.strategy.context.ArrayHandler;

/**
 * 策略:排序策略
 */
public interface Sort {

    /**
     * 排序(context通过参数传递数据给strategy)
     * @param arr
     * @return
     */
     int[] sort(int[] arr);

    /**
     * 排序(context通过context传递数据给strategy)
     * @param arrayHandler
     * @return
     */
     int[] sort(ArrayHandler arrayHandler);

    /**
     * 排序(无需传递数据,context通过ConcreteStrategy中的context引用传递数据给strategy)
     * @return
     */
     int[] sort();

}

具体策略:冒泡排序策略、插入排序策略、选择排序策略,在各个策略中分别使用冒泡、插入、选择排序算法实现排序,arrayHandler引用只在演示第三个方法的时候用到了

package com.ysj.part5.strategy.concreteStrategy;

import com.ysj.part5.strategy.context.ArrayHandler;
import com.ysj.part5.strategy.strategy.Sort;

/**
 * 具体策略:冒泡排序策略
 */
public class BubbleSort implements Sort {

    private ArrayHandler arrayHandler;

    public BubbleSort() {
    }

    public BubbleSort(ArrayHandler arrayHandler) {
        this.arrayHandler = arrayHandler;
    }

    public int[] sort(int[] arr) {
        int len = arr.length;
        for (int i = 0; i < len; i++) {
            for (int j = i + 1; j < len; j++) {
                int temp;
                if (arr[i] > arr[j]) {
                    temp = arr[j];
                    arr[j] = arr[i];
                    arr[i] = temp;
                }
            }
        }
        System.out.println("冒泡排序");
        return arr;
    }

    @Override
    public int[] sort(ArrayHandler arrayHandler) {
        return sort(arrayHandler.getArr());
    }

    @Override
    public int[] sort() {
        return sort(arrayHandler.getArr());
    }
}
package com.ysj.part5.strategy.concreteStrategy;

import com.ysj.part5.strategy.context.ArrayHandler;
import com.ysj.part5.strategy.strategy.Sort;

/**
 * 具体策略:插入排序策略
 */
public class InsertionSort implements Sort {

    private ArrayHandler arrayHandler;

    public InsertionSort() {
    }

    public InsertionSort(ArrayHandler arrayHandler) {
        this.arrayHandler = arrayHandler;
    }

    public int[] sort(int[] arr) {
        int len = arr.length;
        for (int i = 1; i < len; i++) {
            int j;
            int temp = arr[i];
            for (j = i; j > 0; j--) {
                if (arr[j - 1] > temp) {
                    arr[j] = arr[j - 1];
                } else
                    break;
            }
            arr[j] = temp;
        }
        System.out.println("插入排序");
        return arr;
    }

    @Override
    public int[] sort(ArrayHandler arrayHandler) {
        return sort(arrayHandler.getArr());
    }

    @Override
    public int[] sort() {
        return sort(arrayHandler.getArr());
    }
}
package com.ysj.part5.strategy.concreteStrategy;

import com.ysj.part5.strategy.context.ArrayHandler;
import com.ysj.part5.strategy.strategy.Sort;

/**
 * 具体策略:选择排序策略
 */
public class SelectionSort implements Sort {

    private ArrayHandler arrayHandler;

    public SelectionSort() {
    }

    public SelectionSort(ArrayHandler arrayHandler) {
        this.arrayHandler = arrayHandler;
    }

    public int[] sort(int[] arr) {
        int len = arr.length;
        int temp;
        for (int i = 0; i < len; i++) {
            temp = arr[i];
            int j;
            int samllestLocation = i;
            for (j = i + 1; j < len; j++) {
                if (arr[j] < temp) {
                    temp = arr[j];
                    samllestLocation = j;
                }
            }
            arr[samllestLocation] = arr[i];
            arr[i] = temp;
        }
        System.out.println("选择排序");
        return arr;
    }

    @Override
    public int[] sort(ArrayHandler arrayHandler) {
        return sort(arrayHandler.getArr());
    }

    @Override
    public int[] sort() {
        return sort(arrayHandler.getArr());
    }
}

上下文:数组处理器

package com.ysj.part5.strategy.context;

import com.ysj.part5.strategy.strategy.Sort;
import lombok.Getter;
import lombok.Setter;

/**
 * 上下文:数组处理器
 */
@Setter
@Getter
public class ArrayHandler {

    /**
     * 排序策略
     */
    private Sort sortStrategy;

    /**
     * 排序数组(用于传递参数)
     */
    private int[] arr;

    public int[] sort(int[] arr) {
        // 直接将参数传给strategy
        sortStrategy.sort(arr);
        return arr;
    }

    public int[] sortByHandler(int[] arr) {
        // strategy使用context获取参数必须在context中给arr赋值
        this.arr = arr;
        sortStrategy.sort(this);
        return arr;
    }

    public int[] sortByReference(int[] arr) {
        // strategy使用context引用获取参数必须在context中给arr赋值
        this.arr = arr;
        sortStrategy.sort();
        return arr;
    }

}

客户端:客户

package com.ysj.part5.strategy;

import com.ysj.part5.strategy.concreteStrategy.SelectionSort;
import com.ysj.part5.strategy.context.ArrayHandler;
import com.ysj.part5.strategy.strategy.Sort;
import org.junit.jupiter.api.Test;

import java.util.Arrays;

/**
 * 策略模式
 * 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化
 */
public class Client {

    public static ArrayHandler arrayHandler;

    private static int[] arr;

    /**
     * 使用静态代码块初始化数据
     */
    static {
        arr = new int[]{1, 4, 6, 2, 5, 3, 7, 10, 9};
        arrayHandler = new ArrayHandler();
    }

    /**
     * 排序(context通过参数传递数据给strategy)
     */
    @Test
    public void test1() {
        //数据通过参数传递
        arrayHandler.setSortStrategy(new SelectionSort());
        int[] result = arrayHandler.sort(arr);
        // 打印结果
        Arrays.stream(result).forEach(System.out::println);
    }

    /**
     * 排序(context通过context传递数据给strategy)
     */
    @Test
    public void test2() {
        arrayHandler.setSortStrategy(new SelectionSort());
        int[] result = arrayHandler.sortByHandler(arr);
        // 打印结果
        Arrays.stream(result).forEach(System.out::println);
    }

    /**
     * 排序(无需传递数据,context通过ConcreteStrategy中的context引用传递数据给strategy)
     */
    @Test
    public void test3() {
        arrayHandler.setSortStrategy(new SelectionSort(arrayHandler)); //设置具体策略
        int[] result = arrayHandler.sortByReference(arr);
        // 打印结果
        Arrays.stream(result).forEach(System.out::println);
    }

}

已知应用

Spring Security

Spring Security中很多地方使用了策略模式,比如SessionAuthenticationStrategy、InvalidSessionStrategy,用于在不同情况使用不同的实现,还有授权时使用不同的授权策略AbstractAuthorizationStrategy。

Spring Framework

Spring 支持策略模式,可以通过配置文件或注解来定义和选择不同的策略。比如两个实现类共同实现一个接口,在其他地方引入这个接口的时候可以使用@Qualifier指定具体实现类。

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区