vlambda博客
学习文章列表

记Java策略模式实现

记一次策略模式实践

今天给大家说说田忌赛马的故事。如有雷同,纯属巧合!话说在战国时期,群雄割据,硝烟四起,茶余饭后还是少不了娱乐活动的,其中赛马是最火爆的。一天,孙膑看到田忌像个死鸡似的就知道肯定赛马又输给了齐威王,立马抓住田忌去跟齐威王再赛一场。

孙膑:“小忌啊,哥哥看着你心疼啊,哥哥出对策帮你赢一盘如何?”。

田忌听到之后高兴得飞起,瞪大了两只金鱼眼“Really?只要能赢,我赴汤蹈火,以身相许又如何~”。

孙膑心里一万个草泥马在奔腾,差点没噎死自己“滚一边去,我们这盘跟他show hand!”赛马开始,策略模式上场。此处应该有bgm“让我们红尘作伴活得潇潇洒洒 策马奔腾共享人世繁华...呀啊呀啊,呀啊啊啊啊啊啊~”

再举个例子理解一下

小王在和同事正在吹牛时,领导过来了,小王啊,你又在吹牛了?别吹了,干点正活,下周要去春游,你给出几种方案。小王一听去旅游,屁颠屁颠地去干活了。

方案啊,我早想好了,就是把几种方案封装下,让领导抉择。这个其实就是一个设计模式,叫做策略模式。

定义

定义一系列算法,把他们一个一个封装起来,并且使他们可以相互替换

优缺点

优点

多重条件语句if elseswitch case不易维护,而使用策略模式可以避免使用多重条件语句。

策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。

策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。

··

缺点

客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。

策略模式造成很多的策略类。

策略模式组成部分

策略模式把对象本身和运算规则区分开来,因此我们整个模式也分为三个部分。

环境类(Context):用来操作策略的上下文环境,封装算法对高层屏蔽,高层模块只用访问Context,就是小王交给领导选择方案的入口

抽象策略类(Strategy):策略的抽象,算法家族的抽象,通常为一个接口,就是领导一句话给小王安排滴任务

具体策略类(ConcreteStrategy):具体的策略实现,就是小王完成的A、B、C、D各种具体的出行方案,以供领导选择。

区别于传统的实现方式

策略模式造成类膨胀(很多的策略类),这一点我不能接受.小王接到领导滴任务后发现,领导要求他提供二十几种方案供他选择,我相信小王也受不了.Lambda表达式 + 反射 + 注解实现方式结合了一下来解决这点.

寻常策略模式是基于类的,随着策略的增多类会逐渐增多,我这边通过注解的方式,把类级别的缩减到方法级别的,这样就可以只关心少数的类,每新增策略实现就加一个方法去实现就好了

代码实现以及最终效果

1、定义一个策略注解 Strategy,用来标注策略方法

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Strategy {

    /**
     * 不同的模块标识
     * @return
     */

    String module() default "";

    /**
     * 具体的策略类型
     * @return
     */

    String value() default "";
}

2、策略接口

/**
 *
 * @param <T> 策略的入参类型
 * @param <R> 策略的返回值类型
 *
 * @author tiantian
 * @date 20212/4/07
 */

@FunctionalInterface
public interface IStrategyHandler<TR{

    /**
     * 策略
     * @param strategyData 参数
     * @return
     */

    scene(T strategyData);

}

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

/**
 * 加载策略类下的方法
 * @param <T>
 * @param <R>
 */

@Slf4j
public abstract class AbstractStrategyContext<TRimplements ApplicationContextAware {

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


   /**
    * 获得bean 的class
    *
    * @param <K> 类型
    * @return
    */

   abstract <K> Class<K> getClazz();

   /**
    * 返回spring中的beanName
    *
    * @return
    */

   abstract String getBeanName();


   /**
    * 执行函数
    *
    * @param strategy 策略类型
    * @param strategyData    参数
    * @return
    */

   public R execute(Long strategy, T strategyData) {
       IStrategyHandler<T, R> handler = implMap.get(strategy);
       log.info("策略实现集合{}", implMap);
       if (handler == null) {
           throw new ErrorCodeException(ErrorCodeEnum.DATA_NOT_EXIST);
       }
       R apply = handler.scene(strategyData);
       return apply;
   }


   @Override
   @SuppressWarnings("unchecked")
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
       log.info("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 {
               Long key = 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、策略具体的实现类(加上注解就是方法级别)

@Slf4j
@Service
@Strategy(module = "测试策略")
public class SceneStrategyImpl {


     @Strategy(value = 1module = "方案一")
    public IStrategyHandler<String, String> strateg1() {
        return strategyData -> {
        // 省略具体实现逻辑
        System.out.println("===方案一执行了==="+strategyData);
            return "返回方案一";
        };
    }

    @Strategy(value = 2module = "方案二")
    public IStrategyHandler<String, String> strateg2() {
        return strategyData -> {
         // 省略具体实现逻辑
        System.out.println("===方案二执行了==="+strategyData);
            return "返回方案二";
        };
    }

5、将存放策略方法的类和策略上下文关联起来

@Slf4j
@Component
public class SceneStrategyContext extends AbstractStrategyContext {

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

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

6、写个测试类测一下

    @Test
    void test() {
        String one = strategyContext.execute(1l"这是方案一");
        System.out.println(one);
        String two = strategyContext.execute(2l"这是方案二");
        System.out.println(two);
    }

7、执行结果

总结

解决的策略模式类膨胀的问题,省去的大量的条件判断,增强代码扩展,降低维护成本这是我个人感觉比较实用的点,不知道大家有没有更好的实现方式,欢迎一起交流!