【Java】浅谈设计模式 - 策略模式(三)

浅谈设计模式 - 策略模式(三)

lazytimes发布于 1 月 28 日

浅谈设计模式 - 策略模式(三)

前言

这次我们来讲解一下策略模式,策略模式是我们日常开发天天都在用的“模式”,最简单if/else就是策略,而我们用不同的策略(分支)来实现结果的区分。所以策略模式是非常重要的模式,也是理解和应用最为简单的方式(大概)。

这里再次提醒:不要过分拘泥于设计模式的类和形式,只要记住一点:将变与不变抽离的过程就是设计模式

<!-- more -->

什么是策略模式?

策略模式按照最简单的理解就是对if/else的解耦,也是他最常用的场景,最典型的应用场景就是购物的时候,选择用优惠券,还是满2件送一件,或者凑够多少金额满减等等,按照一般的写法,我们经常会写出大量的if/else,在代码量较少的时候,这种写的方式既简单又方便,但是一旦代码复杂,复杂的if/else会让代码越来越屎,策略模式也是为了解决此问题而产生的。

策略模式是一种行为型模式,他将一类相似的行为解耦,并且将策略封装到具体的策略实现类。

策略模式结构图:

下面用一张烂大街的图描绘一下策略模式的结构,切记落实设计模式到代码之后,你会对这个图的印象更加深刻。

【Java】浅谈设计模式 - 策略模式(三)

下面给出一张工厂模式的图,会发现他们长得非常像:

【Java】浅谈设计模式 - 策略模式(三)

什么情况下使用策略模式?

  1. 当代码充斥大量if/else并且他们只是行为不同的时候,建议使用
  2. 将复杂的策略内容封装到单独的类情况下,比如我们的策略内容需要进行非常复杂的计算

策略模式的特点:

  1. 将相似的行为进行封包,客户端指定策略已达到不同行为的切换
  2. 将复杂的业务实现逻辑代码封装到单独的策略,可以通过context组合使用策略

工厂模式和策略模式的异同:

相同点:

  1. 策略的"执行对象"和工厂生产的“抽象对象”,他们都具有相似的行为。
  2. 都是为了抽离过程和结果实现本身。

不同点:

  1. 工厂模式是为了创建对象,而策略是为了解决复杂的if/else嵌套
  2. 工厂模式只需要传递工厂需要的参数,而策略模式则需要具体的实现类支撑。
  3. 工厂模式是创建型设计模式,而策略模式是行为型模式。前者专注于对象的创建过程,后者专注于对象的具体行为

实际案例:

光有理论是不够的,我们来实际操作一下策略模式。这次的场景模拟个人觉得还挺有意思的,看下具体的内容:

场景模拟:

​ 一些交易的系统,在遇到特殊情况的时候,需要进行网络监控或者管理,有时候需要根据某种条件下触发监控或者报警,比如网关接受一笔交易,需要根据交易的校验情况,在不同的校验代码段进行钉钉机器人报警,下面给出几种情况:

  • 查不出必要数据的时候,给出对应的告警。提醒运营人员排查线上环境
  • 当数据量到达指定的限制量的时候,给出风险告警。
  • 当出现黑名单人员进行交易拦截的时候,进行日志记录,不进行警告

...

不使用设计模式:

兵来将挡,谁来土掩,发现那里需要告警,就往对应的地方添加代码,这样子做完成任务是很快,当然代码烂起来也是很快的。下面看一下具体的实现:

/**

* 策略模式:

* 不使用设计模式实现告警

*

* @author zxd

* @version 1.0

* @date 2021/1/26 23:41

*/

public class Main {

/**

* 不使用模式

*/

public static void main(String[] args) {

System.out.println("接受交易");

service1();

service2();

service3();

System.out.println("完成交易");

}

/**

* 模拟触发了业务场景1

* 出现机房断电或者查不出必要数据的时候,给出对应的告警。提醒运营人员排查线上环境

*/

private static void service1() {

// 为了模拟异常情况,我们用 1/0 触发一个异常

try {

// 程序到了这一步算不下去了

int result = 1/0;

System.out.println("具体的业务");

} catch (Exception e) {

System.err.println("警告,服务器出现异常");

System.out.println("开始执行报警");

try {

Thread.sleep(2000);

} catch (InterruptedException ex) {

ex.printStackTrace();

}

System.err.println("执行报警完成");

throw e;

}

}

/**

* 模拟触发了业务场景2

* 当数据量到达指定的限制量的时候,给出风险告警。

*/

private static void service2() {

int limit = 1000;

int count = 2000;

if(count > limit){

System.out.println("开始执行报警");

try {

Thread.sleep(2000);

} catch (InterruptedException ex) {

ex.printStackTrace();

}

//.....

// logger.info("警告,当前数据请求量达到限制值")

System.err.println("执行报警完成");

}

}

/**

* 模拟触发了业务场景3

* 当出现黑名单人员进行交易拦截的时候,进行日志记录,不进行警告

*/

private static void service3() {

boolean flag = true;

if(flag){

// 触犯黑名单:

// logger.info("警告,当前请求");

// 提前退出,结束交易

return;

}

System.out.println("正常完成下面的步骤");

}

}

如上面的所示,单就这个类就以肉眼可见的速度在膨胀代码,特别是如果我们在告警的代码需要大量的操作的时候,我们会把告警的业务和原有的业务逻辑不断纠缠,最后代码就变成了 面向实现编程,下一个接手的人看到这样的代码,也会接着往后面累加,一个臃肿的结构就此诞生了。

上面的代码存在如下的问题:

  1. 当我们需要新增一处监控的时候,需要在对应的代码块增加监控和报警的逻辑
  2. 所有的改动都在一处,如果代码内容复杂会造成业务逻辑混淆
  3. 当告警的业务日趋复杂,告警的代码将变得难以维护

使用工厂模式:

没有学习策略模式的时候,我们尝试使用工厂模式尝试改写一下这一段代码,同时在使用工厂模式之前,我们回顾一下工厂模式的图,下面画图:

【Java】浅谈设计模式 - 策略模式(三)

下面是使用工厂模式设计出来的关系类

+ BlackListStrategy.java 黑名单策略

+ NoResultStrategy.java 无返回值

+ QuantityStrategy.java 数量监控策略

+ 测试类

+ StrategyFactory.java 策略工厂,负责生产需要的策略

策略工厂,用于生产策略:

/**

* @author zhaoxudong

* @version v1.0.0

* @Package : com.headfirst.strategy.factory

* @Description : 策略工厂,根据参数生产对应的策略条件

* @Create on : 2021/1/27 13:24

**/

public class StrategyFactory {

/**

* 创建策略

* @param service

* @return

*/

public CaveatStrategy createStrategy(String service){

// 数量监控

if(Objects.equals(service, "quantity")){

return new QuantityStrategy();

}else if(Objects.equals(service, "noresult")){

// 没有返回值

return new NoResultStrategy();

}else if(Objects.equals(service, "blacklist")){

// 黑名单

return new BlackListStrategy();

}

return null;

}

}

黑名单策略类:

/**

* 当出现黑名单人员进行交易拦截的时候,进行日志记录,不进行警告

*

* @author zxd

* @version 1.0

* @date 2021/1/28 21:52

*/

public class BlackListStrategy implements CaveatStrategy {

@Override

public void warning(Map<String, Object> params) {

boolean flag = Boolean.parseBoolean(params.get("flag").toString());

if (flag) {

System.err.println("触犯黑名单列表,但不警告");

}

}

}

数量监控策略类:

/**

* @author zhaoxudong

* @version v1.0.0

* @Package : com.headfirst.strategy.use

* @Description : 数量监控

* @Create on : 2021/1/27 13:27

**/

public class QuantityStrategy implements CaveatStrategy {

@Override

public void warning(Map<String, Object> params) {

int limit = Integer.parseInt(params.get("limit").toString());

int count = Integer.parseInt(params.get("count").toString());

if(count > limit){

System.err.println("警告,当前数据内容无法获取返回值");

}

}

}

单元测试:

/**

* 单元测试

*

* @author zxd

* @version 1.0

* @date 2021/1/28 22:01

*/

public class Main {

/**

*

* @param args

*/

public static void main(String[] args) {

// 模拟交易流转参数对象

Map<String, Object> objectObjectHashMap = new HashMap<>();

StrategyFactory strategyFactory = new StrategyFactory();

CaveatStrategy strategy = strategyFactory.createStrategy("quantity");

// 表示除数和被除数

objectObjectHashMap.put("limit", "1000");

objectObjectHashMap.put("count", "2000");

strategy.warning(objectObjectHashMap);

strategy = strategyFactory.createStrategy("noresult");

objectObjectHashMap.put("divisor", "1");

objectObjectHashMap.put("dividend", "0");

strategy.warning(objectObjectHashMap);

strategy = strategyFactory.createStrategy("blacklist");

objectObjectHashMap.put("flag", true);

strategy.warning(objectObjectHashMap);

}/*结果如下:

警告,当前数据内容无法获取返回值

触犯黑名单列表,但不警告

*/

}

上面的代码存在如下的问题:

  1. 策略工厂虽然解决了策略的生产问题,但是需要自己指定策略,而且每次更换策略内容会导致工厂的代码也需要随之改动
  2. 维护和扩展都需要依赖工厂,我们每多一个策略都需要更换工厂的内容
  3. 当告警的业务日趋复杂,工厂的代码将会越发的臃肿

使用策略模式:

在具体的实现之前,我们根据上面提到的图,照着模样画葫芦画一个图出来:

【Java】浅谈设计模式 - 策略模式(三)

策略的实现类在上上面的工厂模式,这里给出上下文以及使用的具体方法:

+ StrategyContext 策略上下文

+ 策略模式的单元测试

策略类的上下文:

/**

* 策略的上下文

*

* @author zxd

* @version 1.0

* @date 2021/1/28 22:52

*/

public class StrategyContext {

private CaveatStrategy strategy;

public StrategyContext(CaveatStrategy strategy) {

this.strategy = strategy;

}

public void doStrategy(Map<String, Object> params){

strategy.warning(params);

}

}

策略模式的单元测试:

/**

* 单元测试

*

* @author zxd

* @version 1.0

* @date 2021/1/28 22:53

*/

public class Main {

/**

* 使用策略模式

* @param args

*/

public static void main(String[] args) {

Map<String, Object> objectObjectHashMap = new HashMap<>();

objectObjectHashMap.put("limit", "1000");

objectObjectHashMap.put("count", "2000");

objectObjectHashMap.put("divisor", "1");

objectObjectHashMap.put("dividend", "0");

objectObjectHashMap.put("flag", true);

CaveatStrategy blackListStrategy = new BlackListStrategy();

CaveatStrategy noResultStrategy = new NoResultStrategy();

CaveatStrategy quantityStrategy = new QuantityStrategy();

// 三种策略独立

StrategyContext strategyContext = new StrategyContext(blackListStrategy);

strategyContext.doStrategy(objectObjectHashMap);

StrategyContext strategyContext2 = new StrategyContext(noResultStrategy);

strategyContext2.doStrategy(objectObjectHashMap);

StrategyContext strategyContext3 = new StrategyContext(quantityStrategy);

strategyContext3.doStrategy(objectObjectHashMap);

// 简化一下:

StrategyContext strategyContext4 = new StrategyContext(blackListStrategy);

strategyContext4.doStrategy(objectObjectHashMap);

strategyContext4 = new StrategyContext(noResultStrategy);

strategyContext4.doStrategy(objectObjectHashMap);

strategyContext4 = new StrategyContext(quantityStrategy);

strategyContext4.doStrategy(objectObjectHashMap);

}/*

触犯黑名单列表,但不警告

警告,当前数据内容无法获取返回值

触犯黑名单列表,但不警告

警告,当前数据内容无法获取返回值

*/

}

从上面的内容可以看出,我们只需要把策略传给上下文,上下文会根据传入的策略自动匹配对应的策略执行报警。

但是我们也发现了一些问题:

  1. 代码存在new策略类,这又回到以前不使用工厂的时候情况了
  2. 如果我们用策略组合,虽然少了很多的if/else,但是建立策略的细节依旧在客户端。

答案已经很明显了,策略和工厂双方各有利弊,果断用策略和工厂模式组合起来进行重写。

简单工厂和策略模式结合:

工厂和策略结合之后,这里我们结合context上下文和工厂看一下效果:

/**

* 改写策略的上下文

*

* @author zxd

* @version 1.0

* @date 2021/1/28 22:52

*/

public class StrategyContext {

private CaveatStrategyFactory caveatStrategyFactory = new CaveatStrategyFactory();

public void doStrategy(String service, Map<String, Object> params){

caveatStrategyFactory.createStrategy(service).warning(params);

}

}

这个工厂和上面工厂模式的工厂没有区别,个人为了区分换了个名字:

/**

* 警告策略的生成厂

*

* @author zxd

* @version 1.0

* @date 2021/1/26 23:28

*/

public class CaveatStrategyFactory {

/**

* 创建策略

* @param service 策略

*/

public CaveatStrategy createStrategy(String service){

// 数量监控

if(Objects.equals(service, "quantity")){

return new QuantityStrategy();

}else if(Objects.equals(service, "noresult")){

// 没有返回值

return new NoResultStrategy();

}else if(Objects.equals(service, "blacklist")){

// 黑名单

return new BlackListStrategy();

}

return null;

}

}

上面的代码有了如下的好处:

  1. 客户端不在需要手动new对象,由工厂来完成
  2. 指定策略只需要的参数和指定策略的名称,上下文“自动”帮我们完成结果
  3. 将策略的生成过程和策略的执行过程更进一步的解耦

到此,这样的代码可维护性和阅读性能大大提高,后续如果还需要扩展策略直接实现抽象接口同时工厂新增判断,然后客户端指定新的策略服务名称即可让整个流程自动化。

顺带一提的是,策略和简单工厂的结合是受到了 《大话设计模式》的启发,大致的思路也做了参考,顿时感觉这样才算是有点学以致用的感觉,撸完代码的感觉还是非常快乐。

更好的“策略”:

上面的代码还不是最优的,在spring当中,我们的策略一般会作为一个bean使用,而不需要每次都使用new去构建我们的策略,因为我们的策略基本都是单例的。下面给出一些建议的写法:

这里我们按照单独的策略类为例,他应该如下:

被spring管理的策略bean:

  • NoResultStrategyImpl 无返回值的策略实现bean

/**

* 无结果的业务实现

*

* @author zxd

* @version 1.0

* @date 2021/1/28 23:08

*/

//@Service

public class NoResultStrategyImpl implements CaveatStrategy {

// 一般此处会组合一些mapper或者引入一些日志记录logger

@Override

public void warning(Map<String, Object> params) {

// loggger.info("记录需要的信息");

int divisor = Integer.parseInt(params.get("divisor").toString());

int dividend = Integer.parseInt(params.get("dividend").toString());

try {

int result = dividend / divisor;

} catch (Exception e) {

System.err.println("警告,服务器出现异常");

System.out.println("开始执行报警");

try {

Thread.sleep(2000);

} catch (InterruptedException ex) {

ex.printStackTrace();

}

// logger.info("日志记录");

System.err.println("执行报警完成");

throw e;

}

// 执行一些策略等

}

}

  • SpringCaveaStrategy spring工具类,使用工具类获取注解对应的bean,这样可以实现从一个接口获取他所管理的多个子类(建议自定义service的Bean名称防止冲突)

/**

* 使用Spring 工具获取指定的Bean

*

* @author zxd

* @version 1.0

* @date 2021/1/28 23:11

*/

//@Component

public class SpringCaveaStrategy {

//使用spring编写的工具类进行bean的获取

public CaveatStrategy getBean(String service){

// return SpringUtils.getBean(service);

// 不建议直接调用,做一下null指针判断

return SpringUtils.getBean(service).warning(params);

}

}

在最后我们结合spring实现 单例之后,我们成功将 单例 + 策略 + 简单工厂进行了整合,这样的代码写起来才爽呀,然而现实生活中我们大多数在一个已经建立好的结构上做优化,这时候就需要更多思考了......

总结:

本文在策略模式上做了进一步的深入思考,我对比了一下简单工厂和策略工厂,这两个模式可以说长得还是非常像的,仅仅靠这些简单的案例是不够的,还需要更多的灵活运用。

个人学习的思路一致按照 模仿 -> 熟练 ->创新,同时按照自己的理解去设计场景,这样给自己的学习是很大的,能看到自己知识的模糊点。

如果这篇文章对你有帮助或者有任何建议意见欢迎讨论。后续会更新更多关于设计模式的内容。

阅读 18发布于 1 月 28 日

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议

avatar

lazytimes

赐他一块白石,石上写着新名

12 声望

2 粉丝

0 条评论

得票时间

avatar

lazytimes

赐他一块白石,石上写着新名

12 声望

2 粉丝

宣传栏

浅谈设计模式 - 策略模式(三)

前言

这次我们来讲解一下策略模式,策略模式是我们日常开发天天都在用的“模式”,最简单if/else就是策略,而我们用不同的策略(分支)来实现结果的区分。所以策略模式是非常重要的模式,也是理解和应用最为简单的方式(大概)。

这里再次提醒:不要过分拘泥于设计模式的类和形式,只要记住一点:将变与不变抽离的过程就是设计模式

<!-- more -->

什么是策略模式?

策略模式按照最简单的理解就是对if/else的解耦,也是他最常用的场景,最典型的应用场景就是购物的时候,选择用优惠券,还是满2件送一件,或者凑够多少金额满减等等,按照一般的写法,我们经常会写出大量的if/else,在代码量较少的时候,这种写的方式既简单又方便,但是一旦代码复杂,复杂的if/else会让代码越来越屎,策略模式也是为了解决此问题而产生的。

策略模式是一种行为型模式,他将一类相似的行为解耦,并且将策略封装到具体的策略实现类。

策略模式结构图:

下面用一张烂大街的图描绘一下策略模式的结构,切记落实设计模式到代码之后,你会对这个图的印象更加深刻。

【Java】浅谈设计模式 - 策略模式(三)

下面给出一张工厂模式的图,会发现他们长得非常像:

【Java】浅谈设计模式 - 策略模式(三)

什么情况下使用策略模式?

  1. 当代码充斥大量if/else并且他们只是行为不同的时候,建议使用
  2. 将复杂的策略内容封装到单独的类情况下,比如我们的策略内容需要进行非常复杂的计算

策略模式的特点:

  1. 将相似的行为进行封包,客户端指定策略已达到不同行为的切换
  2. 将复杂的业务实现逻辑代码封装到单独的策略,可以通过context组合使用策略

工厂模式和策略模式的异同:

相同点:

  1. 策略的"执行对象"和工厂生产的“抽象对象”,他们都具有相似的行为。
  2. 都是为了抽离过程和结果实现本身。

不同点:

  1. 工厂模式是为了创建对象,而策略是为了解决复杂的if/else嵌套
  2. 工厂模式只需要传递工厂需要的参数,而策略模式则需要具体的实现类支撑。
  3. 工厂模式是创建型设计模式,而策略模式是行为型模式。前者专注于对象的创建过程,后者专注于对象的具体行为

实际案例:

光有理论是不够的,我们来实际操作一下策略模式。这次的场景模拟个人觉得还挺有意思的,看下具体的内容:

场景模拟:

​ 一些交易的系统,在遇到特殊情况的时候,需要进行网络监控或者管理,有时候需要根据某种条件下触发监控或者报警,比如网关接受一笔交易,需要根据交易的校验情况,在不同的校验代码段进行钉钉机器人报警,下面给出几种情况:

  • 查不出必要数据的时候,给出对应的告警。提醒运营人员排查线上环境
  • 当数据量到达指定的限制量的时候,给出风险告警。
  • 当出现黑名单人员进行交易拦截的时候,进行日志记录,不进行警告

...

不使用设计模式:

兵来将挡,谁来土掩,发现那里需要告警,就往对应的地方添加代码,这样子做完成任务是很快,当然代码烂起来也是很快的。下面看一下具体的实现:

/**

* 策略模式:

* 不使用设计模式实现告警

*

* @author zxd

* @version 1.0

* @date 2021/1/26 23:41

*/

public class Main {

/**

* 不使用模式

*/

public static void main(String[] args) {

System.out.println("接受交易");

service1();

service2();

service3();

System.out.println("完成交易");

}

/**

* 模拟触发了业务场景1

* 出现机房断电或者查不出必要数据的时候,给出对应的告警。提醒运营人员排查线上环境

*/

private static void service1() {

// 为了模拟异常情况,我们用 1/0 触发一个异常

try {

// 程序到了这一步算不下去了

int result = 1/0;

System.out.println("具体的业务");

} catch (Exception e) {

System.err.println("警告,服务器出现异常");

System.out.println("开始执行报警");

try {

Thread.sleep(2000);

} catch (InterruptedException ex) {

ex.printStackTrace();

}

System.err.println("执行报警完成");

throw e;

}

}

/**

* 模拟触发了业务场景2

* 当数据量到达指定的限制量的时候,给出风险告警。

*/

private static void service2() {

int limit = 1000;

int count = 2000;

if(count > limit){

System.out.println("开始执行报警");

try {

Thread.sleep(2000);

} catch (InterruptedException ex) {

ex.printStackTrace();

}

//.....

// logger.info("警告,当前数据请求量达到限制值")

System.err.println("执行报警完成");

}

}

/**

* 模拟触发了业务场景3

* 当出现黑名单人员进行交易拦截的时候,进行日志记录,不进行警告

*/

private static void service3() {

boolean flag = true;

if(flag){

// 触犯黑名单:

// logger.info("警告,当前请求");

// 提前退出,结束交易

return;

}

System.out.println("正常完成下面的步骤");

}

}

如上面的所示,单就这个类就以肉眼可见的速度在膨胀代码,特别是如果我们在告警的代码需要大量的操作的时候,我们会把告警的业务和原有的业务逻辑不断纠缠,最后代码就变成了 面向实现编程,下一个接手的人看到这样的代码,也会接着往后面累加,一个臃肿的结构就此诞生了。

上面的代码存在如下的问题:

  1. 当我们需要新增一处监控的时候,需要在对应的代码块增加监控和报警的逻辑
  2. 所有的改动都在一处,如果代码内容复杂会造成业务逻辑混淆
  3. 当告警的业务日趋复杂,告警的代码将变得难以维护

使用工厂模式:

没有学习策略模式的时候,我们尝试使用工厂模式尝试改写一下这一段代码,同时在使用工厂模式之前,我们回顾一下工厂模式的图,下面画图:

【Java】浅谈设计模式 - 策略模式(三)

下面是使用工厂模式设计出来的关系类

+ BlackListStrategy.java 黑名单策略

+ NoResultStrategy.java 无返回值

+ QuantityStrategy.java 数量监控策略

+ 测试类

+ StrategyFactory.java 策略工厂,负责生产需要的策略

策略工厂,用于生产策略:

/**

* @author zhaoxudong

* @version v1.0.0

* @Package : com.headfirst.strategy.factory

* @Description : 策略工厂,根据参数生产对应的策略条件

* @Create on : 2021/1/27 13:24

**/

public class StrategyFactory {

/**

* 创建策略

* @param service

* @return

*/

public CaveatStrategy createStrategy(String service){

// 数量监控

if(Objects.equals(service, "quantity")){

return new QuantityStrategy();

}else if(Objects.equals(service, "noresult")){

// 没有返回值

return new NoResultStrategy();

}else if(Objects.equals(service, "blacklist")){

// 黑名单

return new BlackListStrategy();

}

return null;

}

}

黑名单策略类:

/**

* 当出现黑名单人员进行交易拦截的时候,进行日志记录,不进行警告

*

* @author zxd

* @version 1.0

* @date 2021/1/28 21:52

*/

public class BlackListStrategy implements CaveatStrategy {

@Override

public void warning(Map<String, Object> params) {

boolean flag = Boolean.parseBoolean(params.get("flag").toString());

if (flag) {

System.err.println("触犯黑名单列表,但不警告");

}

}

}

数量监控策略类:

/**

* @author zhaoxudong

* @version v1.0.0

* @Package : com.headfirst.strategy.use

* @Description : 数量监控

* @Create on : 2021/1/27 13:27

**/

public class QuantityStrategy implements CaveatStrategy {

@Override

public void warning(Map<String, Object> params) {

int limit = Integer.parseInt(params.get("limit").toString());

int count = Integer.parseInt(params.get("count").toString());

if(count > limit){

System.err.println("警告,当前数据内容无法获取返回值");

}

}

}

单元测试:

/**

* 单元测试

*

* @author zxd

* @version 1.0

* @date 2021/1/28 22:01

*/

public class Main {

/**

*

* @param args

*/

public static void main(String[] args) {

// 模拟交易流转参数对象

Map<String, Object> objectObjectHashMap = new HashMap<>();

StrategyFactory strategyFactory = new StrategyFactory();

CaveatStrategy strategy = strategyFactory.createStrategy("quantity");

// 表示除数和被除数

objectObjectHashMap.put("limit", "1000");

objectObjectHashMap.put("count", "2000");

strategy.warning(objectObjectHashMap);

strategy = strategyFactory.createStrategy("noresult");

objectObjectHashMap.put("divisor", "1");

objectObjectHashMap.put("dividend", "0");

strategy.warning(objectObjectHashMap);

strategy = strategyFactory.createStrategy("blacklist");

objectObjectHashMap.put("flag", true);

strategy.warning(objectObjectHashMap);

}/*结果如下:

警告,当前数据内容无法获取返回值

触犯黑名单列表,但不警告

*/

}

上面的代码存在如下的问题:

  1. 策略工厂虽然解决了策略的生产问题,但是需要自己指定策略,而且每次更换策略内容会导致工厂的代码也需要随之改动
  2. 维护和扩展都需要依赖工厂,我们每多一个策略都需要更换工厂的内容
  3. 当告警的业务日趋复杂,工厂的代码将会越发的臃肿

使用策略模式:

在具体的实现之前,我们根据上面提到的图,照着模样画葫芦画一个图出来:

【Java】浅谈设计模式 - 策略模式(三)

策略的实现类在上上面的工厂模式,这里给出上下文以及使用的具体方法:

+ StrategyContext 策略上下文

+ 策略模式的单元测试

策略类的上下文:

/**

* 策略的上下文

*

* @author zxd

* @version 1.0

* @date 2021/1/28 22:52

*/

public class StrategyContext {

private CaveatStrategy strategy;

public StrategyContext(CaveatStrategy strategy) {

this.strategy = strategy;

}

public void doStrategy(Map<String, Object> params){

strategy.warning(params);

}

}

策略模式的单元测试:

/**

* 单元测试

*

* @author zxd

* @version 1.0

* @date 2021/1/28 22:53

*/

public class Main {

/**

* 使用策略模式

* @param args

*/

public static void main(String[] args) {

Map<String, Object> objectObjectHashMap = new HashMap<>();

objectObjectHashMap.put("limit", "1000");

objectObjectHashMap.put("count", "2000");

objectObjectHashMap.put("divisor", "1");

objectObjectHashMap.put("dividend", "0");

objectObjectHashMap.put("flag", true);

CaveatStrategy blackListStrategy = new BlackListStrategy();

CaveatStrategy noResultStrategy = new NoResultStrategy();

CaveatStrategy quantityStrategy = new QuantityStrategy();

// 三种策略独立

StrategyContext strategyContext = new StrategyContext(blackListStrategy);

strategyContext.doStrategy(objectObjectHashMap);

StrategyContext strategyContext2 = new StrategyContext(noResultStrategy);

strategyContext2.doStrategy(objectObjectHashMap);

StrategyContext strategyContext3 = new StrategyContext(quantityStrategy);

strategyContext3.doStrategy(objectObjectHashMap);

// 简化一下:

StrategyContext strategyContext4 = new StrategyContext(blackListStrategy);

strategyContext4.doStrategy(objectObjectHashMap);

strategyContext4 = new StrategyContext(noResultStrategy);

strategyContext4.doStrategy(objectObjectHashMap);

strategyContext4 = new StrategyContext(quantityStrategy);

strategyContext4.doStrategy(objectObjectHashMap);

}/*

触犯黑名单列表,但不警告

警告,当前数据内容无法获取返回值

触犯黑名单列表,但不警告

警告,当前数据内容无法获取返回值

*/

}

从上面的内容可以看出,我们只需要把策略传给上下文,上下文会根据传入的策略自动匹配对应的策略执行报警。

但是我们也发现了一些问题:

  1. 代码存在new策略类,这又回到以前不使用工厂的时候情况了
  2. 如果我们用策略组合,虽然少了很多的if/else,但是建立策略的细节依旧在客户端。

答案已经很明显了,策略和工厂双方各有利弊,果断用策略和工厂模式组合起来进行重写。

简单工厂和策略模式结合:

工厂和策略结合之后,这里我们结合context上下文和工厂看一下效果:

/**

* 改写策略的上下文

*

* @author zxd

* @version 1.0

* @date 2021/1/28 22:52

*/

public class StrategyContext {

private CaveatStrategyFactory caveatStrategyFactory = new CaveatStrategyFactory();

public void doStrategy(String service, Map<String, Object> params){

caveatStrategyFactory.createStrategy(service).warning(params);

}

}

这个工厂和上面工厂模式的工厂没有区别,个人为了区分换了个名字:

/**

* 警告策略的生成厂

*

* @author zxd

* @version 1.0

* @date 2021/1/26 23:28

*/

public class CaveatStrategyFactory {

/**

* 创建策略

* @param service 策略

*/

public CaveatStrategy createStrategy(String service){

// 数量监控

if(Objects.equals(service, "quantity")){

return new QuantityStrategy();

}else if(Objects.equals(service, "noresult")){

// 没有返回值

return new NoResultStrategy();

}else if(Objects.equals(service, "blacklist")){

// 黑名单

return new BlackListStrategy();

}

return null;

}

}

上面的代码有了如下的好处:

  1. 客户端不在需要手动new对象,由工厂来完成
  2. 指定策略只需要的参数和指定策略的名称,上下文“自动”帮我们完成结果
  3. 将策略的生成过程和策略的执行过程更进一步的解耦

到此,这样的代码可维护性和阅读性能大大提高,后续如果还需要扩展策略直接实现抽象接口同时工厂新增判断,然后客户端指定新的策略服务名称即可让整个流程自动化。

顺带一提的是,策略和简单工厂的结合是受到了 《大话设计模式》的启发,大致的思路也做了参考,顿时感觉这样才算是有点学以致用的感觉,撸完代码的感觉还是非常快乐。

更好的“策略”:

上面的代码还不是最优的,在spring当中,我们的策略一般会作为一个bean使用,而不需要每次都使用new去构建我们的策略,因为我们的策略基本都是单例的。下面给出一些建议的写法:

这里我们按照单独的策略类为例,他应该如下:

被spring管理的策略bean:

  • NoResultStrategyImpl 无返回值的策略实现bean

/**

* 无结果的业务实现

*

* @author zxd

* @version 1.0

* @date 2021/1/28 23:08

*/

//@Service

public class NoResultStrategyImpl implements CaveatStrategy {

// 一般此处会组合一些mapper或者引入一些日志记录logger

@Override

public void warning(Map<String, Object> params) {

// loggger.info("记录需要的信息");

int divisor = Integer.parseInt(params.get("divisor").toString());

int dividend = Integer.parseInt(params.get("dividend").toString());

try {

int result = dividend / divisor;

} catch (Exception e) {

System.err.println("警告,服务器出现异常");

System.out.println("开始执行报警");

try {

Thread.sleep(2000);

} catch (InterruptedException ex) {

ex.printStackTrace();

}

// logger.info("日志记录");

System.err.println("执行报警完成");

throw e;

}

// 执行一些策略等

}

}

  • SpringCaveaStrategy spring工具类,使用工具类获取注解对应的bean,这样可以实现从一个接口获取他所管理的多个子类(建议自定义service的Bean名称防止冲突)

/**

* 使用Spring 工具获取指定的Bean

*

* @author zxd

* @version 1.0

* @date 2021/1/28 23:11

*/

//@Component

public class SpringCaveaStrategy {

//使用spring编写的工具类进行bean的获取

public CaveatStrategy getBean(String service){

// return SpringUtils.getBean(service);

// 不建议直接调用,做一下null指针判断

return SpringUtils.getBean(service).warning(params);

}

}

在最后我们结合spring实现 单例之后,我们成功将 单例 + 策略 + 简单工厂进行了整合,这样的代码写起来才爽呀,然而现实生活中我们大多数在一个已经建立好的结构上做优化,这时候就需要更多思考了......

总结:

本文在策略模式上做了进一步的深入思考,我对比了一下简单工厂和策略工厂,这两个模式可以说长得还是非常像的,仅仅靠这些简单的案例是不够的,还需要更多的灵活运用。

个人学习的思路一致按照 模仿 -> 熟练 ->创新,同时按照自己的理解去设计场景,这样给自己的学习是很大的,能看到自己知识的模糊点。

如果这篇文章对你有帮助或者有任何建议意见欢迎讨论。后续会更新更多关于设计模式的内容。

以上是 【Java】浅谈设计模式 - 策略模式(三) 的全部内容, 来源链接: utcz.com/a/109967.html

回到顶部