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

前言

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

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

代码结构图

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

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

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

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

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

代码实现
  1. 定义一个策略注解 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 "";
}
  1. 策略接口,这里的策略接口如果不是很复杂的话可以用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);

}
  1. 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);
           }
       }
   }
}
  1. 订单、产品模块有这种策略模式场景

订单模块的策略模式场景,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;
        };
    }
}
  1. 将存放策略方法的类和策略上下文关联起来

订单模块相关

@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";
  }
}
  1. 代码实现完毕,我们来测试一下
@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的个人博客,禁止未经授权转载,违者依法追究相关法律责任!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值