记Java策略模式实现
记一次策略模式实践
今天给大家说说田忌赛马的故事。如有雷同,纯属巧合!话说在战国时期,群雄割据,硝烟四起,茶余饭后还是少不了娱乐活动的,其中赛马是最火爆的。一天,孙膑看到田忌像个死鸡似的就知道肯定赛马又输给了齐威王,立马抓住田忌去跟齐威王再赛一场。
孙膑:“小忌啊,哥哥看着你心疼啊,哥哥出对策帮你赢一盘如何?”。
田忌听到之后高兴得飞起,瞪大了两只金鱼眼“Really?只要能赢,我赴汤蹈火,以身相许又如何~”。
孙膑心里一万个草泥马在奔腾,差点没噎死自己“滚一边去,我们这盘跟他show hand!”赛马开始,策略模式上场。此处应该有bgm“让我们红尘作伴活得潇潇洒洒 策马奔腾共享人世繁华...呀啊呀啊,呀啊啊啊啊啊啊~”
再举个例子理解一下
小王在和同事正在吹牛时,领导过来了,小王啊,你又在吹牛了?别吹了,干点正活,下周要去春游,你给出几种方案。小王一听去旅游,屁颠屁颠地去干活了。
方案啊,我早想好了,就是把几种方案封装下,让领导抉择。这个其实就是一个设计模式,叫做策略模式。
定义
“定义一系列算法,把他们一个一个封装起来,并且使他们可以相互替换
”
优缺点
优点:
多重条件语句if else
、switch 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<T, R> {
/**
* 策略
* @param strategyData 参数
* @return
*/
R scene(T strategyData);
}
3、AbstractStrategy实现使用Spring提供的扩展点ApplicationContextAware,在系统启动的时候将对应策略方法的存放在Map中,同时对外提供执行入口 execute
/**
* 加载策略类下的方法
* @param <T>
* @param <R>
*/
@Slf4j
public abstract class AbstractStrategyContext<T, R> implements 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 = 1, module = "方案一")
public IStrategyHandler<String, String> strateg1() {
return strategyData -> {
// 省略具体实现逻辑
System.out.println("===方案一执行了==="+strategyData);
return "返回方案一";
};
}
@Strategy(value = 2, module = "方案二")
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、执行结果
总结
解决的策略模式类膨胀的问题,省去的大量的条件判断,增强代码扩展,降低维护成本这是我个人感觉比较实用的点,不知道大家有没有更好的实现方式,欢迎一起交流!