前言
我在之前的文章设计模式学习之策略模式 一文中介绍了策略模式的优点、缺点、实现方法和具体的使用场景。策略类有策略类会增多,造成类膨胀
的缺点,这个缺点我还是比较在意的。因为在实际开发中发现,有时策略的实现没有多少代码,这样的定义成一个策略类反而真的有点浪费,确实造成了类膨胀。自从发现了这个问题,我一直在思考如何解决这种问题,经过思考解决问题的方法已经想出来了,但是一直没有去尝试做出来看看是否真的能解决策略模式 类膨胀的问题
,终于在这周我忙里偷闲用代码实现了我的想法,我自认为利用 Lambda表达式 + 反射 + 注解
组合解决了类膨胀的问题
,这种船新版本实现方案,容我细细道来!
大扎好,我系渣渣辉,探挽懒月,介四里没有挽过的船新版本,挤需体验三番钟,里造会干我一样,爱象节款游戏。 —开心一哈 😄😄
代码结构图
这种实现方法,UML图没法展现其中关系,所以就不贴UML图了,直接上代码的结构图。
主要思想
- 为何会出现类膨胀
策略模式类膨胀的缺点,就是策略类太多的缘故。如果策略类的实现代码不是很多,是否可以将策略不在类的级别上实现?于是想到了利用Lambda表达式的匿名类来实现策略接口,这样不就把实现从类降级到了方法级别了么,哈哈😄😄!!
- 如何将这些策略方法统一起来,方便外部调用
策略方法都在一个类中,这样首先要标注一下方法,哪个是策略方法,哪个是普通方法。这个问题我们用注解来实现,因为在Java 语言中的类、方法、变量、参数和包等都可以用注解来标注。
我想把策略方法存放在Map中,用策略的名字作为Map的key,策略方法作为Map的value。我们在这里用反射来将Lambda实现的匿名类存放到Map中。
代码实现
- 定义一个策略注解 Strategy,用来标注策略方法
/**
*
* 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 "";
}
- 策略接口,这里的策略接口如果不是很复杂的话可以用jdk自带的
java.util.function.Function<T, R>
替代
/**
*
* @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);
}
- AbstractStrategy实现使用Spring提供的扩展点ApplicationContextAware,在系统启动的时候将对应策略方法的存放在Map中,同时对外提供执行入口 execute
@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);
}
}
}
}
- 订单、产品模块有这种策略模式场景
订单模块的策略模式场景,IStrategyHandler的策略方法都在OrderStrategyImpl中实现。
@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中实现
@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;
};
}
}
- 将存放策略方法的类和策略上下文关联起来
订单模块相关
@Slf4j
@Component
public class OrderStrategyContext extends AbstractStrategyContext {
@Override
Class<OrderStrategyImpl> getClazz() {
return OrderStrategyImpl.class;
}
@Override
String getBeanName() {
return "orderStrategyImpl";
}
}
产品模块相关
@Slf4j
@Component
public class ProductStrategyContext extends AbstractStrategyContext {
@Override
Class<ProductStrategyImpl> getClazz() {
return ProductStrategyImpl.class;
}
@Override
String getBeanName() {
return "productStrategyImpl";
}
}
- 代码实现完毕,我们来测试一下
@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的个人博客,禁止未经授权转载,违者依法追究相关法律责任!