001-策略模式学习

策略模式学习

一、为什么使用

解决在不确定的流程下,使用if....else....带来的难维护及难扩展性。

1.1 具体场景

在我们使用if......else......编程时,如果需要在多个不确定的版本中增加else逻辑,就可以考虑使用策略模式。当然在固定的明确逻辑下还是if...else...效率更高。

如:如果是男生就去打篮球,如果是女生就去踢毽子,对于这种已经明确的逻辑,并且后续版本中也不可能在增加逻辑的流程就可以直接使用if...else...相对来说,效率更高。如果是不确定的流程则需要考虑使用策略模式了。

如:接入支付方式,可能现在的版本中需要接入微信跟支付宝,但是后续可能会陆续接入网银,招商,农行,QQ支付等。这种流程中如果我们先前使用if...else...的话,在后续接入其他支付方式时需要修改原有代码增加else逻辑,尽管我们把逻辑都抽成方法,也违背的开闭原则,以及单一原则。我所经历的项目是在下单的时候使用的这种模式,因为每个场景下单都有一套独有的下单逻辑处理。

当然策略模式带来的问题就是策略类会很多。

1.2 概念

该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

也就是说调用者只用调用context环境类来获取具体的策略,来执行,无需直接创建策略。

策略模式

二、如何使用

以上面那个支付场景为例

2.1 创建策略接口

package com.zhoust.design.strategy.intf;
/**
 * @author zhoushengtao
 * @version v1.0
 * @date 2021/11/1 20:42
 * @desc 支付策略接口
 */
public interface PayStrategy {

    /**
     * 声明支付接口
     */
    public void pay();

    /**
     * 声明支付状态接口
     */
    public void payStatus(String payId);
}

2.2 创建策略实现类

两个实现类

2.2.1 AliPay

@Component
@Slf4j
public class AliPay implements PayStrategy {

    public void pay() {
        log.info("我是支付宝,我开始支付宝支付逻辑。。");
    }

    public void payStatus(String payId) {
        log.info("我是支付宝,我开始查询payId={}的支付状态。。",payId);
    }
}

2.2.2 WeiCharPay

@Slf4j
@Component
public class WeiCharPay  implements PayStrategy {

    public void pay() {
        log.info("我是微信支付,我开始微信支付支付逻辑。。");
    }

    public void payStatus(String payId) {
        log.info("我是微信支付,我开始查询payId={}的支付状态。。",payId);
    }
}

2.3 创建环境context

@Component
@Slf4j
public class PayStrategyContext {
    public PayStrategy getPayStrategyByIf(String payCode){

        if("1".equals(payCode)){
            // ailiPay
            return new AliPay();
        }else if ("2".equals(payCode)){
            // 微信
            return new WeiCharPay();
        }else{
            log.error("没有匹配到支付方式");
            return null;
        }
    }
}

2.4 调用

@Autowired
private PayStrategyContext payStrategyContext;

@GetMapping("/testPay")
public String testPay(@RequestParam String payCode){

    PayStrategy payStrategyByIf = payStrategyContext.getPayStrategyByIf(payCode);
    payStrategyByIf.pay();
    payStrategyByIf.payStatus(payCode);
    return "succeed";
}

2.5 优化

上面基本上已经实现了策略模式,也比较好懂,根据不同的 payCode 返回不同的策略,然后去执行向相应的逻辑。

但上述依然存在if...else...,我们可以使用一个map来替换if...else...。

private static ConcurrentHashMap<String, PayStrategy> strategyMap = new ConcurrentHashMap<String, PayStrategy>();
// 淘汰
static {
    strategyMap.put("1",new AliPay());
    strategyMap.put("2",new WeiCharPay());
}
/**
 * 通过map 来判断
 * @param payCode
 * @return
 */
@Deprecated
public PayStrategy getPayStrategyByMap(String payCode){
    if(!StringUtils.hasText(payCode)){
        return null;
    }
    return strategyMap.get(payCode);
}

上面虽然 没有if...else...但是每次增加策略依然需要修改Context,不符合开闭原则,所以我们要动态给Map放对象。

2.6 动态增加策略

动态获取策略的方式有很多。

  1. 比如放到指定目录下,payCode是类名,通过反射创建对应的对象。
  2. 指定目录创建策略类的时候增加一个payCode 属性,在context中通过反射获取到包下对象,将payCode 与策略类对应放入到map中
  3. 通过注解,扫描到后放入contextmap中,这种比较灵活。下面演示这种

2.7 通过注解自动添加到map

2.7.1 创建注解

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PayCodeAnnotation {
    // 必输
    String payCode();
}

2.7.2 标记

加入 PayCodeAnnotation 标记,并显示指定payCode

@Component
@Slf4j
@PayCodeAnnotation(payCode = "1")
public class AliPay implements PayStrategy {

2.7.3 改造 Context

主要思路跟包扫描类似,获取到所有注解类,并便利放入map中

@Component
@Slf4j
@Setter
public class PayStrategyContext {

    private  ConcurrentHashMap<String, PayStrategy> strategyMap ;

    public PayStrategy getPayStrategy(String payCode){
        if(!StringUtils.hasText(payCode)){
            log.error("未获取到对应的payCode:{}",payCode);
            return null;
        }
        return strategyMap.get(payCode);
    }
}

2.7.4 动态放入对象

通过配置类,在启动的时候往 PayStrategyContext 中的 map 放入属性

@Configuration
public class CreateStrategyMapConfig {

    private final ApplicationContext context;

    public CreateStrategyMapConfig(ApplicationContext context) {
        this.context = context;
    }
    @Bean
    public PayStrategyContext getPayStrategyContext(PayStrategyContext payStrategyContext){
        ConcurrentHashMap<String, PayStrategy> strategyMap = new ConcurrentHashMap<String, PayStrategy>();
        // 从上下文中拿到带注解PayCodeAnnotation的对象
        Map<String, Object> payStrategyAnnotations = context.getBeansWithAnnotation(PayCodeAnnotation.class);

        for (String key :payStrategyAnnotations.keySet()) {
            // 获取对象
            PayStrategy payStrategy = (PayStrategy) payStrategyAnnotations.get(key);
            // 获取注解对应的 payCode
            String payCode = payStrategy.getClass().getAnnotation(PayCodeAnnotation.class).payCode();
            // 放入map中
            strategyMap.put(payCode,payStrategy);
        }
        payStrategyContext.setStrategyMap(strategyMap);
        return payStrategyContext;
    }
}

2.7.5 测试:

@RestController
@Slf4j
public class TestPayController {

    @Autowired
    private PayStrategyContext payStrategyContext;

    @GetMapping("/testPay")
    public String testPay(@RequestParam String payCode){

        PayStrategy payStrategy = payStrategyContext.getPayStrategy(payCode);

        if(null == payStrategy){
            log.error("不存在该种支付方式。payCode:{}",payCode);
            return "err";
        }
        payStrategy.pay();
        payStrategy.payStatus("payId13288888");
        return "succeed";
    }

}

日志:

2021-11-01 22:21:09.775  INFO 14300 --- [nio-8001-exec-1] c.zhoust.design.strategy.stategy.AliPay  : 我是支付宝,我开始支付宝支付逻辑。。
2021-11-01 22:21:09.775  INFO 14300 --- [nio-8001-exec-1] c.zhoust.design.strategy.stategy.AliPay  : 我是支付宝,我开始查询payId=payId13288888的支付状态。。
2021-11-01 22:21:13.842  INFO 14300 --- [nio-8001-exec-4] c.z.design.strategy.stategy.WeiCharPay   : 我是微信支付,我开始微信支付支付逻辑。。
2021-11-01 22:21:13.842  INFO 14300 --- [nio-8001-exec-4] c.z.design.strategy.stategy.WeiCharPay   : 我是微信支付,我开始查询payId=payId13288888的支付状态。。
2021-11-01 22:21:17.199 ERROR 14300 --- [nio-8001-exec-3] c.z.d.s.controller.TestPayController     : 不存在该种支付方式。payCode:4

2.7.6 拓展

基于上述,如果我们要新增加一种云闪付的方式,只需要增加对应策略类即可,无需修改任何文件,如下,增加@PayCodeAnnotation(payCode = "3")注解即可。

@Component
@Slf4j
@PayCodeAnnotation(payCode = "3")
public class UnionPay implements PayStrategy {

    public void pay() {
        log.info("我是云闪付,我开始云闪付支付逻辑。。");
    }
    public void payStatus(String payId) {
        log.info("我是云闪付,我开始查询payId={}的支付状态。。",payId);
    }
}

源码地址:design · zhoust/zhoustcomm - 码云 - 开源中国 (gitee.com)

posted @ 2021-11-01 22:32  神经哇  阅读(75)  评论(0)    收藏  举报