关于策略模式、模板模式和工厂模式的基础概念和优缺点能够自行了解一下,这里主要讲的是如何优雅地使用这三种模式保证服务符合:SRP(单一职责原则)和OCP(开闭原则)、耦合度低、可扩展性高和减小大量if else代码的场景。前端
策略模式:es6
1.环境(Context)角色:持有一个Strategy的引用。算法
2.抽象策略(Strategy)角色:这是一个抽象角色,一般由一个接口或抽象类实现。此角色给出全部的具体策略类所需的接口。spring
3.具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。bash
这种的经典并简单的策略模式你们也许已经使用过了,可是这样的策略模式会有一个缺点:app
策略模式适用于多类型场景,调用策略时一定会有大量的if else,后续若有新的类型的策略须要被使用时则须要增长if else,代码改动较大,从而致使该模块可扩展性不高且会产生大量if else代码,不易维护。ide
为更好体验到优化的过程,首先给一个需求背景:工具
某服务须要展现给用户三种不一样的趋势图:指数变化趋势图、新增课件数趋势图、新增点评数趋势图。优化
模块优化Round 1:ui
为了解决以上的问题,使用策略模式+自定义注解+模板模式(模板模式和优化无关,只是业务须要)来设计一下:
首先须要抽象父类策略的定义:
@Slf4j
public abstract class AbstractTrendChartHandler {
private final EducationManager educationManager;
public List<TrendChartDataVo> handle(EducationQuery query){
return getTrendChartData(query);
}
private List<TrendChartDataVo> getTrendChartData(EducationQuery query) {
DateRangeQueryVo dateRangeQueryVo = new DateRangeQueryVo();
dateRangeQueryVo.setStartDate(LocalDate.now().minusWeeks(TrendChartConstant.towMonthsWeeks));
dateRangeQueryVo.setEndDate(LocalDate.now());
List<TrendChartDataVo> trendChartDataVos = educationManager.getTrendChartData(query.getAreaCode(),
AreaRankingType.Companion.toAreaRankingType(query.getAreaRankingType()), dateRangeQueryVo);
return getValuesOperation(trendChartDataVos);
}
/**
* 趋势图的每一个点的数值处理(默认是返回与前七天平均值的差值)
*
* @param trendChartDataVos
* @return
*/
protected List<TrendChartDataVo> getValuesOperation(List<TrendChartDataVo> trendChartDataVos) {
if (CollectionUtils.isEmpty(trendChartDataVos) || trendChartDataVos.size() < TrendChartConstant.towMonthsWeeks) {
return new ArrayList<>();
}
List<TrendChartDataVo> newTrendChartDataVos = new ArrayList<>();
IntStream.range(0, trendChartDataVos.size()).forEach(i -> {
if (i == 0){
return;
}
TrendChartDataVo trendChartDataVo = new TrendChartDataVo();
trendChartDataVo.setDate(trendChartDataVos.get(i).getDate());
trendChartDataVo.setValue(trendChartDataVos.get(i).getValue() - trendChartDataVos.get(i - 1).getValue());
newTrendChartDataVos.add(trendChartDataVo);
});
return newTrendChartDataVos;
}
@Autowired
public AbstractTrendChartHandler(EducationManager educationManager) {
this.educationManager = educationManager;
}
}复制代码
能够看到,handle(EducationQuery query)方法是统一处理方法,子类能够继承也能够默认使用父类方法。getTrendChartData(EducationQuery query)是模板方法,子类必定会执行该方法,并能够重写父类的getValuesOperation(List<TrendChartDataVo> trendChartDataVos)方法。
三个子类以下:
@Service
@Slf4j
@TrendChartType(TrendChartTypeEnum.COURSEWARE_COUNT)
public class NewClassesHandler extends AbstractTrendChartHandler {
@Autowired
public NewClassesHandler(EducationManager educationManager) {
super(educationManager);
}
}复制代码
@Service
@Slf4j
@TrendChartType(TrendChartTypeEnum.COMMENT_COUNT)
public class NewCommentsHandler extends AbstractTrendChartHandler {
@Autowired
public NewCommentsHandler(EducationManager educationManager) {
super(educationManager);
}
}复制代码
@Service
@Slf4j
@TrendChartType(TrendChartTypeEnum.SCHOOL_INDEX)
public class PigeonsIndexHandler extends AbstractTrendChartHandler {
public List<TrendChartDataVo> getValuesOperation(List<TrendChartDataVo> trendChartDataVos) {
if (trendChartDataVos.size() <= TrendChartConstant.towMonthsWeeks) {
return new ArrayList<>();
}
trendChartDataVos.remove(0);
return trendChartDataVos;
}
@Autowired
public PigeonsIndexHandler(EducationManager educationManager) {
super(educationManager);
}
}复制代码
能够看到,子类NewClassesHandler和NewCommentsHandler类都默认使用了父类的模板,实现了这两种趋势图的逻辑。而PigeonsIndexHandler类则重写了父类的模板中的方法,使用了父类的逻辑后使用子类中重写的逻辑。
以上是策略规则,接下来是策略获取类 TrendChartHandlerContext类:
@SuppressWarnings("unchecked")
public class TrendChartHandlerContext {
private Map<Integer, Class> handlerMap;
TrendChartHandlerContext(Map<Integer, Class> handlerMap) {
this.handlerMap = handlerMap;
}
public AbstractTrendChartHandler getInstance(Integer type) {
Class clazz = handlerMap.get(type);
if (clazz == null) {
throw new IllegalArgumentException("not found handler for type: " + type);
}
return (AbstractTrendChartHandler) BeanTool.getBean(clazz);
}
}复制代码
该类用于将前端传入的type转为子类对象。
TrendChartHandlerContext中的handlerMap的值则为这三种趋势图的类型的枚举中的值。由
TrendChartHandlerProcessor类统一扫描自定义注解的值,并统一将类型和子类对象放入handlerMap中。
使用策略:
/**
* 查看指数/新增课件数/新增点评数走势图
*
* @param query
* @return
*/
@GetMapping("/charts")
public Object queryDeviceBrand(@Validated(value = {EducationGroup.GetTrendChart.class}) EducationQuery query) {
return ResultBuilder.create().ok().data(educationService.queryTrendChartData(query)).build();
}复制代码
service逻辑实现:
public List<TrendChartDataVo> queryTrendChartData(EducationQuery query) {
return trendChartHandlerContext.getInstance(query.getAreaRankingType()).handle(query);
}复制代码
能够看到,使用策略时只须要调用策略的handler方法便可,无需关注type,规避掉大量的if else代码。
工具类:
@Component
public class BeanTool implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
if (applicationContext == null) {
applicationContext = context;
}
}
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
}复制代码
public class ClassScaner implements ResourceLoaderAware {
private final List<TypeFilter> includeFilters = new LinkedList<>();
private final List<TypeFilter> excludeFilters = new LinkedList<>();
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
@SafeVarargs
public static Set<Class<?>> scan(String[] basePackages, Class<? extends Annotation>... annotations) {
ClassScaner cs = new ClassScaner();
if (ArrayUtils.isNotEmpty(annotations)) {
for (Class anno : annotations) {
cs.addIncludeFilter(new AnnotationTypeFilter(anno));
}
}
Set<Class<?>> classes = new HashSet<>();
for (String s : basePackages) {
classes.addAll(cs.doScan(s));
}
return classes;
}
@SafeVarargs
public static Set<Class<?>> scan(String basePackages, Class<? extends Annotation>... annotations) {
return ClassScaner.scan(StringUtils.tokenizeToStringArray(basePackages, ",; \t\n"), annotations);
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourcePatternResolver = ResourcePatternUtils
.getResourcePatternResolver(resourceLoader);
this.metadataReaderFactory = new CachingMetadataReaderFactory(
resourceLoader);
}
public final ResourceLoader getResourceLoader() {
return this.resourcePatternResolver;
}
public void addIncludeFilter(TypeFilter includeFilter) {
this.includeFilters.add(includeFilter);
}
public void addExcludeFilter(TypeFilter excludeFilter) {
this.excludeFilters.add(0, excludeFilter);
}
public void resetFilters(boolean useDefaultFilters) {
this.includeFilters.clear();
this.excludeFilters.clear();
}
public Set<Class<?>> doScan(String basePackage) {
Set<Class<?>> classes = new HashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ org.springframework.util.ClassUtils
.convertClassNameToResourcePath(SystemPropertyUtils
.resolvePlaceholders(basePackage))
+ "/**/*.class";
Resource[] resources = this.resourcePatternResolver
.getResources(packageSearchPath);
for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
if (resource.isReadable()) {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if ((includeFilters.size() == 0 && excludeFilters.size() == 0) || matches(metadataReader)) {
try {
classes.add(Class.forName(metadataReader
.getClassMetadata().getClassName()));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
} catch (IOException ex) {
throw new BeanDefinitionStoreException(
"I/O failure during classpath scanning", ex);
}
return classes;
}
protected boolean matches(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return true;
}
}
return false;
}
}复制代码
这样就算解决类if else的问题了,可是你们会发现有大量的工具须要引入,增长了许多代码量,看上去会不够美观。
模块优化Round 2:
未完待续