文章目录
- 1.概述
- 1.1.简述策略模式
- 2.实现方法
- 2.1.实现思路
- 2.2.实现代码
- 2.3.策略拓展
- 2.4.执行调用
- 3.总结
1.概述
本篇文章主要会描述SpringBoot与策略模式的结合使用,因为不涉及到理论部分,所以在阅读本篇之前,需要对策略模式的理论已经有了一个基本的了解。
1.1.简述策略模式
策略模式有3种角色,分别为:选择器、抽象策略、策略实例。
其中选择器selector
又被称为上下文context
,其作用为通过不同的标识来获取对应的策略实例。策略实例就是封装不同算法的实例对象,而抽象策略就是策略实例的顶层接口。
简单类图大概就是这个样子:
2.实现方法
我们在学习设计模式的时候会发现在各类模式中的类与对象都是手动创建的,而在日常的开发中,我们往往会将对象的生命周期交给Spring管理,也就是说,需要我们自行将各类bean
组合成一个可运行的设计模式。
假设我们有这样一个场景,需要对系统的中的数据做统计,需求中的统计维度分为:按周统计、按月统计,现使用策略模式来实现这个需求。
2.1.实现思路
- 前置设计:通过定义常量来标识策略的类型,使用者调用时可以通过常量获取对应的策略实例。
- 策略设计:按周、按月分别对应两个
bean
实例,在内部各自实现对应的统计维度逻辑,在两个bean
实例的上层是抽象策略,有一个通用的接口(或抽象类)用于对外提供访问入口。 - 选择器设计:可以通过
Map
来存储数据,调用者调用时可以通过策略标识来获取对应的策略实例。
可以看到,实现思路是比较简单点的,现在的问题就是如何把策略的bean
实例对象放到Map
中。
最简单的方式当然就是把对应的bean
的Class
对象直接写死在Map
中,调用的时候可以通过applicationContext.getBean()
获取到bean
实例。但是这种方式不利于拓展,后续要新增一个策略实例的时候,还得修改这里的Map
。
第二种方式,我们可以通过解析注解来实现,给每个策略实例打上一个注解,注解中的值对应的就是按周、按月这样的常量标识,在SpringBoot启动时,通过扩展点扫描抽象策略,获取它所有的策略实例,解析注解后放入Map
中。这种方式利于拓展,新增一个策略实例不需要对旧代码有任何改动。
2.2.实现代码
说了半天,不如直接看一下代码实现。
- 第一步:定义注解:
import java.lang.annotation.*; /** * 统计策略注解 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Inherited @Documented public @interface StatisticStrategyAnno { String value(); }
- 第二步:编写抽象策略与策略实例
/** * 统计抽象策略处理器 */ public interface StatisticBaseHandler { void doStatistic(); }
import org.springframework.stereotype.Component; /** * 月统计策略 */ @Component @StatisticStrategyAnno("month") public class StatisticByMonthHandler implements StatisticBaseHandler { @Override public void doStatistic() { System.out.println("StatisticByMonthHandler"); } }
import org.springframework.stereotype.Component; /** * 周统计策略 */ @Component @StatisticStrategyAnno("week") public class StatisticByWeekHandler implements StatisticBaseHandler { @Override public void doStatistic() { System.out.println("StatisticByWeekHandler"); } }
- 第三步:编写选择器
import org.jetbrains.annotations.NotNull; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import java.util.Map; import java.util.stream.Collectors; /** * 统计策略选择器 */ @Component public class StatisticStrategySelector implements ApplicationContextAware { private Map<String, StatisticBaseHandler> selectorMap; /** * 根据类型选择对应的策略 * * @param type 统计周期类型 * @return 统计抽象策略处理器 */ public StatisticBaseHandler select(String type) { return selectorMap.get(type); } @Override public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException { // 通过注解获取所有的统计策略处理器,并将其注入到map中 this.selectorMap = applicationContext.getBeansOfType(StatisticBaseHandler.class).values().stream() .filter(strategy -> strategy.getClass().isAnnotationPresent(StatisticStrategyAnno.class)) .collect(Collectors.toMap(strategy -> strategy.getClass().getAnnotation(StatisticStrategyAnno.class).value(), strategy -> strategy)); } }
启动完成之后,Map
中的值如下图所示。
2.3.策略拓展
现在需求发生了变化,需要加入一个按年统计的策略类型,只需要新增一个策略实例即可,如下:
import org.springframework.stereotype.Component;
/**
* 年统计策略
*/
@Component
@StatisticStrategyAnno("year")
public class StatisticByYearHandler implements StatisticBaseHandler {
@Override
public void doStatistic() {
System.out.println("StatisticByYearHandler");
}
}
其他的代码都不需要修改,再次查看Map
中的值:
2.4.执行调用
随便写了一个测试方法,如下:
public void invoke() {
this.doInvoke("week");
this.doInvoke("month");
this.doInvoke("year");
}
public void doInvoke(String type) {
StatisticBaseHandler handler = select(type);
handler.doStatistic();
}
分别打印出了按周统计、按月统计、按年统计方法中的输出值,表示策略模式定义成功了,在我们实际的开放中,只需要前端(或其他调用端)传入对用的标识字符串,就可以执行不同的统计逻辑了。
3.总结
通过Spring获取接口的实现,并解析实现类上的注解的方式,可以在程序启动时动态的将策略注入到一个Map
中,作为策略的容器。使用时传入标识符
就可以获取到对应的策略执行了。