XiaoLin's Blog

Xiao Lin

策略设计模式学习笔记

4
2024-02-26

策略设计模式是一种将算法从使用算法的代码中分离出来的设计模式。这种模式允许算法独立于其使用方式进行更改,从而提高了代码的灵活性。

策略设计模式的结构如下:

  • 策略接口:定义算法的接口。
  • 具体策略:实现策略接口的具体算法。
  • 上下文:使用策略接口的对象。

策略设计模式的优点包括:

  • 灵活性:算法可以独立于其使用方式进行更改。
  • 可重用性:算法可以被多个上下文对象重用。
  • 可测试性:算法可以更轻松地进行测试。

策略设计模式的缺点包括:

  • 复杂性:策略设计模式比简单地将算法嵌入代码中更复杂。
  • 性能:策略设计模式可能比简单地将算法嵌入代码中更慢。

策略设计模式适用于以下场景:

  • 算法可能会经常更改。
  • 算法需要被多个上下文对象重用。
  • 算法需要被测试。

策略设计模式的示例包括:

  • 排序算法:排序算法可以作为策略实现,并由上下文对象(例如数组或链表)使用。
  • 搜索算法:搜索算法可以作为策略实现,并由上下文对象(例如文件系统或数据库)使用。
  • 压缩算法:压缩算法可以作为策略实现,并由上下文对象(例如文件或流)使用。

总之,策略设计模式是一种将算法从使用算法的代码中分离出来的设计模式。这种模式允许算法独立于其使用方式进行更改,从而提高了代码的灵活性。

java 实现策略模式

根据上文,策略模式需要的角色有 3 个

策略接口
具体策略
上下文

假设我们要实现一个计算器,支持加法、减法和乘法运算。我们可以使用策略模式将各种运算独立为不同的策略,并让客户端根据需要选择和使用不同的策略。

  1. 首先,定义一个 计算策略的抽象接口(策略接口)
package xyz.xiaolinz.demo.strategy;  
  
/**  
 * 计算器demo 策略模式 - 策略接口  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2024/02/04  
 */
 public interface Calculation {  
  
    /**  
     * 做计算  
     *  
     * @param num1  
     * @param num2  
     * @return double  
     * @author huangmuhong  
     * @date 2024/02/04  
     * @since 1.0.0  
     */    
     double doCalculation(double num1, double num2);  
  
}
  1. 接下来实现具体的策略实现以实现我们对加减乘除的需求
/**  
 * 策略模式demo - 计算器 加法具体策略  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2024/02/04  
 * @see Calculation  
 */  
public class Addition implements Calculation {  
    @Override  
    public double doCalculation(double num1, double num2) {  
        return num1 + num2;  
    }  
}

/**  
 * 策略模式demo - 计算器 减法具体策略  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2024/02/04  
 * @see Calculation  
 */  
public class Subtraction implements Calculation {  
    @Override  
    public double doCalculation(double num1, double num2) {  
        return num1 - num2;  
    }  
}

/**  
 * 策略模式demo - 计算器 乘法具体策略  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2024/02/04  
 * @see Calculation  
 */  
public class Multiplication implements Calculation {  
    @Override  
    public double doCalculation(double num1, double num2) {  
        return num1 * num2;  
    }  
}

/**  
 * 策略模式demo - 计算器 除法具体策略  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2024/02/04  
 * @see Calculation  
 */  
public class Division implements Calculation {  
    @Override  
    public double doCalculation(double num1, double num2) {  
        return num1 / num2;  
    }  
}
  1. 创建我们的上下文角色,实现对策略的具体封装屏蔽直接调用策略
package xyz.xiaolinz.demo.strategy.calculator;  
  
/**  
 * 策略模式demo - 计算器 上下文类  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2024/02/04  
 */
 public class Calculator {  
  
    private Calculation calculation;  
  
    public Calculator(Calculation calculation) {  
        this.calculation = calculation;  
    }  
  
    public void setCalculation(Calculation calculation) {  
        this.calculation = calculation;  
    }  
  
    /**  
     * 计算  
     *  
     * @param num1 数字1  
     * @param num2 数字2  
     * @return double  
     * @author huangmuhong  
     * @date 2024/02/04  
     * @since 1.0.0  
     */    
     public double compute(double num1, double num2) {  
        return calculation.doCalculation(num1, num2);  
    }  
  
}
  1. 接下来可以通过 Calculator 上下文类去操作封装的策略,已达到执行不同运算的效果
package xyz.xiaolinz.demo.strategy.calculator;  
  
/**  
 * 主要  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2024/02/04  
 */public class Main {  
  
    public static void main(String[] args) {  
        // 定义需要计算的值  
        double num1 = 100;  
        double num2 = 200;  
          
        // 创建策略上下文  
        final Calculator calculator = new Calculator();  
        // 设置具体的策略  
        // step1. 加法  
        calculator.setCalculation(new Addition());  
        System.out.println("加法策略计算结果:" + calculator.compute(num1,num2));  
          
        // step2. 减法  
        calculator.setCalculation(new Subtraction());  
        System.out.println("减法策略计算结果:" + calculator.compute(num1,num2));  
          
        // step3. 乘法  
        calculator.setCalculation(new Multiplication());  
        System.out.println("乘法策略计算结果:" + calculator.compute(num1,num2));  
          
        // step4. 除法  
        calculator.setCalculation(new Division());  
        System.out.println("除法策略计算结果:" + calculator.compute(num1,num2));  
    }  
}

结果:
image.png

在这个例子中,我们使用策略模式将加法、减法和乘法运算独立为不同的策略。客户端可以根据需要选择和使用不同的策略。Calculator 上下文类持有一个 Operation 策略对象,并通过 setOperation 方法允许客户端设置所需的策略。这种方式使得算法的选择和执行更加灵活,易于扩展和维护。

策略模式的优点包括:

  1. 提高代码的可维护性和可扩展性。当需要添加新的算法时,我们只需要实现一个新的具体策略类,而无需修改客户端代码。
  2. 符合开闭原则。策略模式允许我们在不修改现有代码的情况下引入新的策略。
  3. 避免使用多重条件判断。使用策略模式可以消除一些复杂的条件判断语句,使代码更加清晰和易于理解。

策略模式的缺点包括:

  1. 客户端需要了解所有的策略。为了选择合适的策略,客户端需要了解不同策略之间的区别。
  2. 增加了类的数量。策略模式会导致程序中具体策略类的数量增加,这可能会导致代码的复杂性增加。

策略模式实现小技巧

结合工厂模式的策略实现

策略模式是一种设计模式,它允许你将算法和使用算法的代码分开。这使得你可以轻松地修改算法,而无需修改使用算法的代码。

工厂模式是一种设计模式,它允许你将对象的创建与使用对象的过程分开。这使得你可以轻松地创建不同类型的对象,而无需修改使用这些对象的过程。

策略模式结合工厂模式可以让你轻松地修改算法,而无需修改使用算法的代码。你还可以轻松地创建不同类型的对象,而无需修改使用这些对象的过程。

以下是策略模式结合工厂模式的一个示例:

/**  
* 计算器demo 策略模式 - 策略接口  
*  
* @author huangmuhong  
* @version 1.0.0  
* @date 2024/02/04  
*/
public interface Calculation {  
 
   /**  
    * 做计算  
    *  
    * @param num1  
    * @param num2  
    * @return double  
    * @author huangmuhong  
    * @date 2024/02/04  
    * @since 1.0.0  
    */    
    double doCalculation(double num1, double num2);  
 
}

/**  
* 策略模式demo - 计算器 加法具体策略  
*  
* @author huangmuhong  
* @version 1.0.0  
* @date 2024/02/04  
* @see Calculation  
*/  
public class Addition implements Calculation {  
   @Override  
   public double doCalculation(double num1, double num2) {  
       return num1 + num2;  
   }  
}

/**  
* 策略模式demo - 计算器 减法具体策略  
*  
* @author huangmuhong  
* @version 1.0.0  
* @date 2024/02/04  
* @see Calculation  
*/  
public class Subtraction implements Calculation {  
   @Override  
   public double doCalculation(double num1, double num2) {  
       return num1 - num2;  
   }  
}

/**  
* 策略模式demo - 计算器工厂  
*  
* @author huangmuhong  
* @version 1.0.0  
* @date 2024/02/05  
*/
public class CalculationFactory {  
 
   private static final Map<String, Calculation> calculationMap = new HashMap<>();  
 
   /**  
    * 获取计算实例  
    *  
    * @param calculationType 计算类型  
    * @return {@link Calculation }  
    * @author huangmuhong  
    * @date 2024/02/05  
    * @since 1.0.0  
    */    
    public Calculation getCalculationInstance(String calculationType) {  
 
       return calculationMap.getOrDefault(calculationType, createInstance(calculationType));  
 
   }  
 
   /**  
    * 创建实例  
    *  
    * @param calculationType 计算类型  
    * @return {@link Calculation }  
    * @author huangmuhong  
    * @date 2024/02/05  
    * @since 1.0.0  
    */   
     private Calculation createInstance(String calculationType) {  
       switch (calculationType) {  
           case "addition":  
               return new Addition();  
           case "subtraction":  
               return new Subtraction();  
           default:  
               throw new IllegalArgumentException("calculationType is not supported");  
       }  
   }  
 
}

package xyz.xiaolinz.demo.strategy.factory;  
 
/**  
* 策略模式demo - 计算器 上下文类  
*  
* @author huangmuhong  
* @version 1.0.0  
* @date 2024/02/04  
*/
public class Calculator {  
 
   private final CalculationFactory calculationFactory = new CalculationFactory();  
 
   private Calculation calculation;  
 
   /**  
    * 设定计算策略  
    *  
    * @param calculationType 计算类型  
    * @author huangmuhong  
    * @date 2024/02/05  
    * @since 1.0.0  
    */    
    public void setCalculation(String calculationType) {  
       this.calculation = calculationFactory.getCalculationInstance(calculationType);  
   }  
 
   /**  
    * 计算  
    *  
    * @param num1 数字1  
    * @param num2 数字2  
    * @return double  
    * @author huangmuhong  
    * @date 2024/02/04  
    * @since 1.0.0  
    */    
    public double compute(double num1, double num2) {  
       return calculation.doCalculation(num1, num2);  
   }  
 
}

public class Main {  
   public static void main(String[] args) {  
       Calculator calculator = new Calculator();  
       calculator.setCalculation("addition");  
       System.out.println("1 + 2 = " + calculator.compute(1, 2));  
       calculator.setCalculation("subtraction");  
       System.out.println("1 - 2 = " + calculator.compute(1, 2));  
   }  
}

结果:

image.png

使用策略模式消除代码中的 if-else

在平常的开发中 if-else 是我们很常用的语法函数,但是当在迭代过程中,if-else 的分支过于的庞大臃肿(有时会超过上千行),这时候就会导致代码的可读性和维护性降低。

我们可以通过策略模式来优化这种庞大的 if-else 分支,将具体业务抽象出策略接口,将每一个 if-else 的分支当作为具体的策略实现,最终我们可以将代码优化成符合开闭原则的代码
以下是一个小 demo

这是一个包含大量 if 分支的报文解析系统:

public class MessageParser {
    public void parseMessage(Message message) {
        String messageType = message.getType();

        if ("XML".equalsIgnoreCase(messageType)) {
            // 解析 XML 报文
            System.out.println("解析 XML 报文: " + message.getContent());
        } else if ("JSON".equalsIgnoreCase(messageType)) {
            // 解析 JSON 报文
            System.out.println("解析 JSON 报文: " + message.getContent());
        } else if ("CSV".equalsIgnoreCase(messageType)) {
            // 解析 CSV 报文
            System.out.println("解析 CSV 报文: " + message.getContent());
        } else {
            throw new IllegalArgumentException("未知的报文类型: " + messageType);
        }
    }
}

我们根据上述的 if-else 分支可以发现,这是一个报文解析系统的核心逻辑。
我们可以通过抽象出一个报文解析接口(策略接口),以及将具体的解析封装成具体的策略实现即可

  1. 抽取策略接口 ParseStrategy
package xyz.xiaolinz.demo.strategy.erase;  
  
/**  
 * 策略模式 - 消除if-else demo 解析策略  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2024/02/05  
 */
 public interface ParseStrategy {  
  
    /**  
     * 解析  
     *  
     * @param content 内容  
     * @return 解析结果  
     */  
    String parse(String content);  
}
  1. 实现具体的解析策略
/**  
 * @author huangmuhong  
 * @date 2024/2/5  
 */
 public class CsvParseStrategy implements ParseStrategy {  
    @Override  
    public String parse(String content) {  
        return "csv parse: " + content;  
    }  
}

/**
 * @author huangmuhong
 * @date 2024/2/5
 */
public class JsonParseStrategy implements ParseStrategy {
    @Override
    public String parse(String content) {
        return "json parse: " + content;
    }
}

/**  
 * @author huangmuhong  
 * @date 2024/2/5  
 */
 public class XmlParseStrategy implements ParseStrategy {  
    @Override  
    public String parse(String content) {  
        return "xml parse: " + content;  
    }  
}
  1. 定义策略工厂和上下文类,封装调用
  • 工厂
package xyz.xiaolinz.demo.strategy.erase;  
  
import java.util.Map;  
  
/**  
 * 解析策略工厂  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2024/02/05  
 */  
public class ParseStrategyFactory {  
  
    public static final ParseStrategyFactory INSTANCE = new ParseStrategyFactory();  
  
    private static final Map<String, ParseStrategy> parseStrategyMap =  
        Map.of("json", new JsonParseStrategy(), "xml", new XmlParseStrategy(), "csv", new CsvParseStrategy());  
  
    private ParseStrategyFactory() {  
    }  
    
    /**  
     * 获取解析策略  
     *  
     * @param type 类型  
     * @return {@link ParseStrategy }  
     * @author huangmuhong  
     * @date 2024/02/05  
     * @since 1.0.0  
     */    
     public ParseStrategy getParseStrategy(String type) {  
        return parseStrategyMap.get(type);  
    }  
}
  • 上下文类
package xyz.xiaolinz.demo.strategy.erase;  
  
/**  
 * 解析上下文  
 *  
 * @author huangmuhong  
 * @version 1.0.0  
 * @date 2024/02/05  
 */
 public class ParseContext {  
  
    private final ParseStrategyFactory parseStrategyFactory = ParseStrategyFactory.INSTANCE;  
  
    private ParseStrategy parseStrategy;  
  
    public void setParseStrategy(String type) {  
        this.parseStrategy = parseStrategyFactory.getParseStrategy(type);  
    }  
  
    /**  
     * 解析  
     *  
     * @param content 内容  
     * @return 解析结果  
     */  
    public String parse(String content) {  
        if (parseStrategy == null) {  
            throw new RuntimeException("parse strategy is not set");  
        }  
        return parseStrategy.parse(content);  
    }  
  
}
  1. 使用
package xyz.xiaolinz.demo.strategy.erase;  
  
/**  
 * @author huangmuhong  
 * @date 2024/2/5  
 */public class Main {  
    public static void main(String[] args) {  
        ParseContext parseContext = new ParseContext();  
        parseContext.setParseStrategy("csv");  
        System.out.println(parseContext.parse("content"));  
          
        parseContext.setParseStrategy("json");  
        System.out.println(parseContext.parse("content"));  
          
        parseContext.setParseStrategy("xml");  
        System.out.println(parseContext.parse("content"));  
    }  
}

结果:
image.png

源码使用

ssm 框架

  1. Spring中的Resource接口:在Spring框架中,org.springframework.core.io.Resource接口用于抽象不同类型的资源,例如文件系统资源、类路径资源、URL资源等。Resource接口就像策略模式中的策略接口,而不同类型的资源类(如ClassPathResourceFileSystemResource等)就像具体策略。客户端可以根据需要选择和使用不同的资源类。
  2. Spring中的AOP代理:在Spring AOP中,代理类的创建使用了策略模式。org.springframework.aop.framework.ProxyFactory中的AopProxy接口定义了创建代理对象的策略接口,而JdkDynamicAopProxyCglibAopProxy这两个类分别为基于JDK动态代理和CGLIB动态代理的具体策略。客户端可以根据需要选择使用哪种代理方式。
  3. MyBatis中的Executor接口:在MyBatis中,Executor接口定义了执行SQL语句的策略接口。MyBatis提供了不同的Executor实现,例如SimpleExecutorReuseExecutorBatchExecutor等,它们分别表示不同的执行策略。客户端可以通过配置选择使用哪种执行策略。
  4. Spring MVC中的HandlerMapping接口:在Spring MVC框架中,HandlerMapping接口定义了映射请求到处理器的策略接口。Spring MVC提供了多种HandlerMapping实现,例如BeanNameUrlHandlerMappingRequestMappingHandlerMapping等,分别表示不同的映射策略。客户端可以通过配置选择使用哪种映射策略。

这些例子展示了策略模式在SSM框架中的应用。策略模式通过将算法和客户端分离,使得系统更加灵活和可扩展。在实际开发中,我们可以参考这些例子,根据业务需求和系统架构灵活地运用策略模式。

这里我们以MyBatis中的Executor接口为例,展示策略模式在MyBatis中的应用。

首先,Executor接口是策略接口,定义了执行SQL语句的公共方法。以下是简化后的Executor接口:

public interface Executor {

  <E> List<E> query(MappedStatement ms, Object parameter) throws SQLException;

  int update(MappedStatement ms, Object parameter) throws SQLException;

  // ... 其他方法
}

接下来,我们来看MyBatis提供的不同Executor实现:

(1)SimpleExecutor:简单执行器,每次执行SQL都会创建一个新的预处理语句(PreparedStatement)。

public class SimpleExecutor extends BaseExecutor {

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    // ... 省略具体实现
  }

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter) throws SQLException {
    // ... 省略具体实现
  }

  // ... 其他方法
}

(2)ReuseExecutor:重用执行器,会尽量重用预处理语句(PreparedStatement),以减少创建和销毁预处理语句的开销。

public class ReuseExecutor extends BaseExecutor {

    @Override
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        // ... 省略具体实现
    }

    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameter) throws SQLException {
        // ... 省略具体实现
    }

    // ... 其他方法
}

(3)BatchExecutor:批处理执行器,可以将多个SQL语句一起发送到数据库服务器,减少网络开销。

public class BatchExecutor extends BaseExecutor {

    @Override
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        // ... 省略具体实现
    }

    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameter) throws SQLException {
        // ... 省略具体实现
    }

    // ... 其他方法
}

客户端可以通过配置选择使用哪种执行策略。在MyBatis配置文件(mybatis-config.xml)中,我们可以设置<setting>标签的defaultExecutorType属性来指定执行器类型:

<settings>
  <setting name="defaultExecutorType" value="SIMPLE" />
  <!-- 可选值:SIMPLE, REUSE, BATCH -->
</settings>

在这个例子中,Executor接口就像策略模式中的策略接口,而SimpleExecutorReuseExecutorBatchExecutor这三个类就像具体策略。客户端可以根据需要选择和使用不同的执行器类型。这种方式使得SQL执行策略的选择和实现更加灵活和可扩展。

jdk源码

下面我们以java.util.Comparator接口为例,展示策略模式在JDK中的应用。

假设我们有一个Student类,表示学生。我们需要对一个Student对象的列表进行排序。根据不同的需求,我们可能需要按照学生的姓名、年龄或成绩进行排序。这时,我们可以使用策略模式,通过实现Comparator接口,为不同的排序需求提供不同的比较策略。

首先,定义Student类:

public class Student {
    private String name;
    private int age;
    private double score;

    // 构造方法、getter和setter方法省略
}

然后,实现Comparator接口,定义不同的比较策略:

public class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return s1.getName().compareTo(s2.getName());
    }
}

// 根据学生的年龄进行排序
public class AgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return Integer.compare(s1.getAge(), s2.getAge());
    }
}

// 根据学生的成绩进行排序
public class ScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return Double.compare(s1.getScore(), s2.getScore());
    }
}

最后,在客户端代码中,根据需要选择和使用不同的比较策略:

public class Client {
    public static void main(String[] args) {
        // 创建一个Student对象的列表
        List<Student> students = new ArrayList<>();
        students.add(new Student("Alice", 20, 90.0));
        students.add(new Student("Bob", 18, 85.0));
        students.add(new Student("Charlie", 22, 88.0));

        // 使用姓名比较策略进行排序
        Collections.sort(students, new NameComparator());
        System.out.println("按姓名排序: " + students);

        // 使用年龄比较策略进行排序
        Collections.sort(students, new AgeComparator());
        System.out.println("按年龄排序: " + students);

        // 使用成绩比较策略进行排序
        Collections.sort(students, new ScoreComparator());
        System.out.println("按成绩排序: " + students);
    }
}