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 动态增加策略
动态获取策略的方式有很多。
- 比如放到指定目录下,
payCode是类名,通过反射创建对应的对象。 - 指定目录创建策略类的时候增加一个
payCode属性,在context中通过反射获取到包下对象,将payCode与策略类对应放入到map中 - 通过注解,扫描到后放入
context的map中,这种比较灵活。下面演示这种
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);
}
}

浙公网安备 33010602011771号