Java设计模式——策略

前言

策略模式是平时Java开发中常用的一种,虽然已有很多讲解设计模式的文章,但是这里还是写篇文章来从自己理解的角度讲解一下。

使用场景

我们不妨进行场景假设,要对我们的软件进行授权管理:在启动我们的软件之前先要校验是否存在合法的授权,如果授权不合法则要求用户进行激活操作。作为例子,我们就简单地实现一下授权校验功能:分发的授权文件中内容是一个四位随机数,并且最后一位是数字且为0。我们只要校验授权文件中内容的最后一位是数字0即可。

java 复制代码
public class LicenseService {
    public boolean checkLicense() {
        boolean result = false;
        // abc0
        File file = Path.of("./secret").toFile();
        String content = "";
        try{
            // 读取文件内容
            BufferedReader br = new BufferedReader(new FileReader(file));
            content = br.readLine();
            br.close();
        }catch(Exception e){
            e.printStackTrace();
        }
		// 末尾字符是0即认为校验通过
        if (content.endsWith("0")) {
            result = true;
        }

        return result;
    }
}

需求变更

现在需求进行了变更,不再校验末尾字符为0了,而是校验开头字符是0,因此我们需要对程序进行修改。并且,我们在调整程序的过程中将读取文件内容和授权校验的逻辑进行分离,将授权校验的逻辑抽到一个单独的方法中。

java 复制代码
public boolean checkLicense() {
        ...

        result = checkInternal(content, result);

        ...
    }

    private static boolean checkInternal(String content, boolean result) {
        if (content.startsWith("0")) {
            result = true;
        }
        return result;
    }

改完之后又接到了最新通知,还有可能改回原来末尾字符的判断方式,于是我们又对方法进行了调整。通过方法传入一个参数来决定使用哪种方式判断:

java 复制代码
public boolean checkLicense() {
        ...

        result = checkInternal(content, result, 1);

        ...
    }

    private static boolean checkInternal(String content, boolean result, int choose) {
    	// 通过方法传入的choose来决定使用哪种算法
        if (choose == 0) {
            if (content.endsWith("0")) {
                result = true;
            }
        } else if (choose == 1) {
            if (content.startsWith("0")) {
                result = true;
            }
        }
        return result;
    }

策略模式

上面我们的例子是比较简单的,但是达到了演示的效果:校验授权 的实现可能有多个版本,并且不同版本的实现都有可能被使用。为了后续方便扩展和维护,我们把checkInternal 方法中的两个if判断中的逻辑再抽离出来。

首先定义一个策略接口:

java 复制代码
public interface CheckStrategy {
    boolean check(String content);
}

然后将两个if中的逻辑转到接口的实现类中:

java 复制代码
public class CheckStart implements CheckStrategy {
    @Override
    public boolean check(String content) {
        boolean result = false;
        if (content.startsWith("0")) {
            result = true;
        }
        return result;
    }
}
java 复制代码
public class CheckEnd implements CheckStrategy {
    @Override
    public boolean check(String content) {
        boolean result = false;
        if (content.endsWith("0")) {
            result = true;
        }
        return result;
    }
}

接下来再调整一下LicenseService中方法的调用,把原来的checkInternal方法中的if语句进行调整,改为调用CheckStrategy中的方法:

java 复制代码
public boolean checkLicense() {
        ...

        result = checkInternal(content, new CheckStart());

        ...
}

private static boolean checkInternal(String content, CheckStrategy strategy) {
        return strategy.check(content);
}

更多思考

有说一种对于策略的说法是替换满屏的if else,我认为这不能算是策略模式的使用目的,只能算是应用了策略模式后的副产物。

它更多的使用场景是这样:有某一大方法,其中的一个环节可以有不同实现,并且进行环节的算法替换时不影响原大方法的功能(或受到预期的控制)。这里再举一些应用场景的例子,不够精准但重在体会其中的思想:

  • 实现游戏可以用不同的底层引擎,引擎之间的替换可以应用策略模式
  • 程序和数据库交互时可能用到不同的数据库产品如mysql、sqllite,对不同数据库的交互操作可以应用策略模式
  • 别的我暂时想不起来了

Spring中实战

现在java web应用里Spring算是事实上的标准了,在Spring中用策略模式还是有一些小技巧的。下面直接给出代码请同学们品。

java 复制代码
@Service
public class LicenseService {
    // 注入strategy manager
    @Autowired
    private StrategyManager strategyManager;

    public boolean checkLicense() {
        boolean result = false;
        // abc0
        File file = Path.of("./secret").toFile();
        String content = "";
        try {
            // 读取文件内容
            BufferedReader br = new BufferedReader(new FileReader(file));
            content = br.readLine();
            br.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 由manager作为策略类实现的提供者
        result = strategyManager
                .pickCheckStrategy(CheckStrategyEnum.START.toString())
                .check(content);

        return result;
    }
}
java 复制代码
@Service
public class StrategyManager {
    // 注入CheckStrategy list
    @Autowired
    private List<CheckStrategy> checkStrategyList;

    public CheckStrategy pickCheckStrategy(String type) {
        // 根据传入的type从上面list中取出对应的策略实现类并返回给调用者
        return checkStrategyList
                .stream()
                .filter(s -> s.type().equals(type))
                .findFirst()
                .orElseThrow();
    }
}

enum CheckStrategyEnum {
    END, START;
}
java 复制代码
public interface CheckStrategy {
    /**
     * 返回策略实现类的类型,用于为manager提供实现类的标识
     *
     * @return 自定义的枚举类型 {@link CheckStrategyEnum}
     */
    String type();

    /**
     * 判断授权。算法由实现类确定
     *
     * @param content 判断的内容
     * @return 是否判断成功
     */
    boolean check(String content);
}
java 复制代码
@Service
public class CheckStart implements CheckStrategy {
    @Override
    public String type() {
        // 返回对应的枚举type
        return CheckStrategyEnum.END.toString();
    }

    @Override
    public boolean check(String content) {
        boolean result = false;
        if (content.startsWith("0")) {
            result = true;
        }
        return result;
    }
}
java 复制代码
@Service
public class CheckEnd implements CheckStrategy {
    @Override
    public String type() {
        // 返回对应的枚举type
        return CheckStrategyEnum.START.toString();
    }

    @Override
    public boolean check(String content) {
        boolean result = false;
        if (content.endsWith("0")) {
            result = true;
        }
        return result;
    }
}
相关推荐
Reese_Cool12 分钟前
【数据结构与算法】排序
java·c语言·开发语言·数据结构·c++·算法·排序算法
TheITSea1 小时前
云服务器宝塔安装静态网页 WordPress、VuePress流程记录
java·服务器·数据库
AuroraI'ncoding1 小时前
SpringMVC接收请求参数
java
九圣残炎1 小时前
【从零开始的LeetCode-算法】3354. 使数组元素等于零
java·算法·leetcode
天天扭码2 小时前
五天SpringCloud计划——DAY1之mybatis-plus的使用
java·spring cloud·mybatis
程序猿小柒2 小时前
leetcode hot100【LeetCode 4.寻找两个正序数组的中位数】java实现
java·算法·leetcode
不爱学习的YY酱3 小时前
【操作系统不挂科】<CPU调度(13)>选择题(带答案与解析)
java·linux·前端·算法·操作系统
丁总学Java3 小时前
Maven项目打包,com.sun.tools.javac.processing
java·maven
kikyo哎哟喂3 小时前
Java 代理模式详解
java·开发语言·代理模式
duration~3 小时前
SpringAOP模拟实现
java·开发语言