船新版本的策略模式,你一定没有见过

我在之前的文章设计模式学习之策略模式 一文中介绍了策略模式的优点、缺点、实现方法和具体的使用场景。策略类有策略类会增多,造成类膨胀的缺点,这个缺点我还是比较在意的。因为在实际开发中发现,有时策略的实现没有多少代码,这样的定义成一个策略类反而真的有点浪费,确实造成了类膨胀。自从发现了这个问题,我一直在思考如何解决这种问题,经过思考解决问题的方法已经想出来了,但是一直没有去尝试做出来看看是否真的能解决策略模式 类膨胀的问题,终于在这周我忙里偷闲用代码实现了我的想法,我自认为利用 Lambda表达式 + 反射 + 注解 组合解决了类膨胀的问题,这种船新版本实现方案,容我细细道来!

大扎好,我系渣渣辉,探挽懒月,介四里没有挽过的船新版本,挤需体验三番钟,里造会干我一样,爱象节款游戏。 —开心一哈 😄😄

代码结构图

这种实现方法,UML图没法展现其中关系,所以就不贴UML图了,直接上代码的结构图。

船新版本策略模式
主要思想
  • 为何会出现类膨胀

策略模式类膨胀的缺点,就是策略类太多的缘故。如果策略类的实现代码不是很多,是否可以将策略不在类的级别上实现?于是想到了利用Lambda表达式的匿名类来实现策略接口,这样不就把实现从类降级到了方法级别了么,哈哈😄😄!!

  • 如何将这些策略方法统一起来,方便外部调用
    策略方法都在一个类中,这样首先要标注一下方法,哪个是策略方法,哪个是普通方法。这个问题我们用注解来实现,因为在Java 语言中的类、方法、变量、参数和包等都可以用注解来标注。

我想把策略方法存放在Map中,用策略的名字作为Map的key,策略方法作为Map的value。我们在这里用反射来将Lambda实现的匿名类存放到Map中。

代码实现
  1. 定义一个策略注解 Strategy,用来标注策略方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /**
    *
    * Strategy 注解
    *
    * @author Sam
    * @date 2021/1/26
    * @since 1.7.7
    */
    @Inherited
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD,ElementType.TYPE})
    public @interface Strategy {

    /**
    * 标识不同的模块 product、order模块
    * @return
    */
    String module() default "";

    /**
    * 具体的策略类型
    * @return
    */
    String value() default "";
    }
  2. 策略接口,这里的策略接口如果不是很复杂的话可以用jdk自带的java.util.function.Function<T, R>替代

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    *
    * @param <T> 策略的入参类型
    * @param <R> 策略的返回值类型
    *
    * @author Sam
    * @date 2021/1/26
    * @since 1.7.7
    */
    @FunctionalInterface
    public interface IStrategyHandler<T, R> {

    /**
    * 策略
    * @param param 参数
    * @return
    */
    R apply(T param);

    }
  3. AbstractStrategy实现使用Spring提供的扩展点ApplicationContextAware,在系统启动的时候将对应策略方法的存放在Map中,同时对外提供执行入口 execute

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    @Slf4j
    public abstract class AbstractStrategyContext<T, R> implements ApplicationContextAware {

    private Map<String, IStrategyHandler<T, R>> implMap = new ConcurrentHashMap<>();

    private final String DELIMITER = "####";

    /**
    * 获得bean 的class
    *
    * @param <K> 类型
    * @return
    */
    abstract <K> Class<K> getClazz();

    /**
    * 返回spring中的beanName
    *
    * @return
    */
    abstract String getBeanName();


    /**
    * 执行函数
    *
    * @param strategy 策略类型
    * @param module 模块
    * @param param 参数
    * @return
    */
    public R execute(String strategy, String module, T param) {

    String key = StringUtils.join(module, DELIMITER, strategy);
    IStrategyHandler<T, R> handler = implMap.get(key);
    log.debug("策略实现集合{}", implMap);
    if (handler == null) {
    throw new RuntimeException("没有相应的策略实现");
    }
    R apply = handler.apply(param);
    return apply;
    }


    @Override
    @SuppressWarnings("unchecked")
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    log.error("AbstractStrategy 执行");

    Class<Object> clazz = getClazz();
    Object bean = applicationContext.getBean(getBeanName(), clazz);
    if (bean == null) {
    return;
    }
    Strategy strategyAnnotation = bean.getClass().getAnnotation(Strategy.class);
    if (strategyAnnotation == null) {
    log.error("类[{}]没有添加Strategy注解", clazz);
    return;
    }
    // 模块的名称
    String module = strategyAnnotation.module();
    Method[] declaredMethods = clazz.getDeclaredMethods();

    if (ArrayUtils.isEmpty(declaredMethods)) {
    throw new RuntimeException(clazz + "没有相关策略方法");
    }

    for (Method declaredMethod : declaredMethods) {
    Strategy annotation = declaredMethod.getAnnotation(Strategy.class);
    if (annotation == null) {
    continue;
    }
    try {
    // 用module和 四个 #### 加上 value 组成map的key
    String key = StringUtils.join(module, DELIMITER, annotation.value());
    IStrategyHandler<T, R> handler = (IStrategyHandler<T, R>) declaredMethod.invoke(bean);
    implMap.put(key, handler);
    } catch (IllegalAccessException | InvocationTargetException e) {
    log.error("模块[{}]策略处理发生了错误", module, e);
    }
    }
    }
    }
  4. 订单、产品模块有这种策略模式场景

订单模块的策略模式场景,IStrategyHandler的策略方法都在OrderStrategyImpl中实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@Slf4j
@Service
@Strategy(module = "Order")
public class OrderStrategyImpl {

@Autowired
private BizLocalMessageService bizLocalMessageService;


@Strategy(value = "strategy1")
public IStrategyHandler<BInDto, BOutDto> strategy1() {

return bInDto -> {
log.error("订单模块相关策略 strategy1 executeStart");
String join = StringUtils.join("strategy1:", bInDto.getParam1(), bInDto.getParam2());
BOutDto bOutDto = new BOutDto();
bOutDto.setOrderNo(join);
bOutDto.setPrice(111);

SaveBizLocalMessageDto dto = new SaveBizLocalMessageDto();
dto.setBizModule((byte)0);

ThreadLocalRandom localRandom = ThreadLocalRandom.current();
dto.setBizNo(localRandom.nextInt(16000,115000)+"");
dto.setBizType((byte)0);
dto.setMsg("");
dto.setMsgDesc("");
dto.setHandledCount((byte)0);
dto.setMaxHandleCount((byte)0);

bizLocalMessageService.saveBizLocalMessage(dto);
log.error("订单模块相关策略 strategy1 executeEnd");
return bOutDto;
};
}


@Strategy(value = "strategy2")
public IStrategyHandler<BInDto, BOutDto> strategy2() {

return param -> {

log.error("订单模块相关策略 strategy2 executeStart");

String join = StringUtils.join("strategy2:", param.getParam1(), param.getParam2());
BOutDto bOutDto = new BOutDto();
bOutDto.setOrderNo(join);
bOutDto.setPrice(11121);

SaveBizLocalMessageDto dto = new SaveBizLocalMessageDto();
dto.setBizModule((byte)0);

ThreadLocalRandom localRandom = ThreadLocalRandom.current();
dto.setBizNo(localRandom.nextInt(6000,15000)+"");

dto.setBizType((byte)0);
dto.setMsg("");
dto.setMsgDesc("");
dto.setHandledCount((byte)0);
dto.setMaxHandleCount((byte)0);

bizLocalMessageService.saveBizLocalMessage(dto);
log.error("订单模块相关策略 strategy2 executeEnd");
return bOutDto;
};
}
}

产品模块的策略模式场景,IStrategyHandler的策略方法都在ProductStrategyImpl中实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Slf4j
@Service
@Strategy(module = "Product")
public class ProductStrategyImpl {

@Autowired
private BizLocalMessageService bizLocalMessageService;


@Strategy(value = "strategyA")
public IStrategyHandler<BInDto, BOutDto> strategy1() {

return bInDto -> {

log.error("产品模块相关策略 strategy1 executeStart");

String join = StringUtils.join("strategyA:", bInDto.getParam1(), bInDto.getParam2());
BOutDto bOutDto = new BOutDto();
bOutDto.setOrderNo(join);
bOutDto.setPrice(111);

SaveBizLocalMessageDto dto = new SaveBizLocalMessageDto();
dto.setBizModule((byte)0);
ThreadLocalRandom localRandom = ThreadLocalRandom.current();
dto.setBizNo(localRandom.nextInt(1000,5000)+"");
dto.setBizType((byte)0);
dto.setMsg("");
dto.setMsgDesc("");
dto.setHandledCount((byte)0);
dto.setMaxHandleCount((byte)0);

bizLocalMessageService.saveBizLocalMessage(dto);

log.error("产品模块相关策略 strategy1 executeEnd");
return bOutDto;
};
}


@Strategy(value = "strategyB")
public IStrategyHandler<BInDto, BOutDto> strategy2() {

return param -> {

log.error("产品模块相关策略 strategy2 executeStart");

String join = StringUtils.join("strategyB:", param.getParam1(), param.getParam2());
BOutDto bOutDto = new BOutDto();
bOutDto.setOrderNo(join);
bOutDto.setPrice(11121);

SaveBizLocalMessageDto dto = new SaveBizLocalMessageDto();
dto.setBizModule((byte)0);
ThreadLocalRandom localRandom = ThreadLocalRandom.current();
dto.setBizNo(localRandom.nextInt(5000,8000)+"");
dto.setBizType((byte)0);
dto.setMsg("");
dto.setMsgDesc("");
dto.setHandledCount((byte)0);
dto.setMaxHandleCount((byte)0);

bizLocalMessageService.saveBizLocalMessage(dto);

log.error("产品模块相关策略 strategy2 executeEnd");
return bOutDto;
};
}
}
  1. 将存放策略方法的类和策略上下文关联起来

订单模块相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@Component
public class OrderStrategyContext extends AbstractStrategyContext {

@Override
Class<OrderStrategyImpl> getClazz() {
return OrderStrategyImpl.class;
}

@Override
String getBeanName() {
return "orderStrategyImpl";
}
}

产品模块相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@Component
public class ProductStrategyContext extends AbstractStrategyContext {

@Override
Class<ProductStrategyImpl> getClazz() {
return ProductStrategyImpl.class;
}

@Override
String getBeanName() {
return "productStrategyImpl";
}
}
  1. 代码实现完毕,我们来测试一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@ContextConfiguration(locations = {"classpath:spring/spring-dao.xml","classpath:spring/spring-service.xml"})
@RunWith(value = SpringJUnit4ClassRunner.class)
public class StrategyTest {

@Autowired
private AbstractStrategy<BInDto, BOutDto> orderStrategyContext;

@Autowired
private AbstractStrategy<BInDto, BOutDto> productStrategyContext;


@Test
public void productTest(){
BInDto bInDto = new BInDto();
bInDto.setParam1("产品金额");
bInDto.setParam2(222);

productStrategyContext.execute("strategyA", "Product", bInDto);
productStrategyContext.execute("strategyB", "Product", bInDto);
}

@Test
public void orderTest(){
BInDto bInDto = new BInDto();
bInDto.setParam1("优惠券金额");
bInDto.setParam2(180);

orderStrategyContext.execute("strategy1", "Order", bInDto);
orderStrategyContext.execute("strategy2", "Order", bInDto);
}
}

产品模块的测试
产品模块的测试

订单模块的测试

订单模块的测试
总结

策略接口不是很复杂的情况下 IStrategyHandler 接口可以复用,自己实现 StrategyContext 抽象类,然后再实现自己的策略方法就可以了,不知大家对于这种船新版本有什么看法!

  • 作者: Sam
  • 发布时间: 2021-01-30 15:06:49
  • 最后更新: 2021-02-14 12:07:41
  • 文章链接: https://ydstudios.gitee.io/post/cd1b2f51.html
  • 版权声明: 本网所有文章除特别声明外, 禁止未经授权转载,违者依法追究相关法律责任!