我在之前的文章设计模式学习之策略模式 一文中介绍了策略模式的优点、缺点、实现方法和具体的使用场景。策略类有策略类会增多,造成类膨胀
的缺点,这个缺点我还是比较在意的。因为在实际开发中发现,有时策略的实现没有多少代码,这样的定义成一个策略类反而真的有点浪费,确实造成了类膨胀。自从发现了这个问题,我一直在思考如何解决这种问题,经过思考解决问题的方法已经想出来了,但是一直没有去尝试做出来看看是否真的能解决策略模式 类膨胀的问题
,终于在这周我忙里偷闲用代码实现了我的想法,我自认为利用 Lambda表达式 + 反射 + 注解
组合解决了类膨胀的问题
,这种船新版本实现方案,容我细细道来!
大扎好,我系渣渣辉,探挽懒月,介四里没有挽过的船新版本,挤需体验三番钟,里造会干我一样,爱象节款游戏。 —开心一哈 😄😄
代码结构图
这种实现方法,UML图没法展现其中关系,所以就不贴UML图了,直接上代码的结构图。
主要思想
策略模式类膨胀的缺点,就是策略类太多的缘故。如果策略类的实现代码不是很多,是否可以将策略不在类的级别上实现?于是想到了利用Lambda表达式的匿名类来实现策略接口,这样不就把实现从类降级到了方法级别了么,哈哈😄😄!!
- 如何将这些策略方法统一起来,方便外部调用
策略方法都在一个类中,这样首先要标注一下方法,哪个是策略方法,哪个是普通方法。这个问题我们用注解来实现,因为在Java 语言中的类、方法、变量、参数和包等都可以用注解来标注。
我想把策略方法存放在Map中,用策略的名字作为Map的key,策略方法作为Map的value。我们在这里用反射来将Lambda实现的匿名类存放到Map中。
代码实现
定义一个策略注解 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
|
@Inherited @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.TYPE}) public @interface Strategy {
String module() default "";
String value() default ""; }
|
策略接口,这里的策略接口如果不是很复杂的话可以用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
|
@FunctionalInterface public interface IStrategyHandler<T, R> {
R apply(T param);
}
|
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 = "####";
abstract <K> Class<K> getClazz();
abstract String getBeanName();
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 { 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); } } } }
|
订单、产品模块有这种策略模式场景
订单模块的策略模式场景,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 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 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 抽象类,然后再实现自己的策略方法就可以了,不知大家对于这种船新版本有什么看法!