策略模式&模板模式&工厂模式 如何优雅地用在项目中

关于策略模式、模板模式和工厂模式的基础概念和优缺点可以自行了解一下,这里主要讲的是如何优雅地使用这三种模式保证服务符合:SRP(单一职责原则)和OCP(开闭原则)、耦合度低、可扩展性高和减少大量if else代码的场景。

策略模式:

1.环境(Context)角色:持有一个Strategy的引用。

2.抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。

3.具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

这种的经典并简单的策略模式大家也许已经使用过了,但是这样的策略模式会有一个缺点:

策略模式适用于多类型场景,调用策略时必定会有大量的if else,后续如有新的类型的策略需要被使用时则需要增加if else,代码改动较大,从而导致该模块可扩展性不高且会产生大量if else代码,不易维护。

为更好体验到优化的过程,首先给一个需求背景:

某服务需要展示给用户三种不同的趋势图:指数变化趋势图、新增课件数趋势图、新增点评数趋势图。

模块优化Round 1:

为了解决以上的问题,使用策略模式+自定义注解+模板模式(模板模式和优化无关,只是业务需要)来设计一下:

首先需要抽象父类策略的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
复制代码@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 trendChartDataVos)方法。

三个子类如下:

1
2
3
4
5
6
7
8
9
10
复制代码@Service
@Slf4j
@TrendChartType(TrendChartTypeEnum.COURSEWARE_COUNT)
public class NewClassesHandler extends AbstractTrendChartHandler {

@Autowired
public NewClassesHandler(EducationManager educationManager) {
super(educationManager);
}
}
1
2
3
4
5
6
7
8
9
10
复制代码@Service
@Slf4j
@TrendChartType(TrendChartTypeEnum.COMMENT_COUNT)
public class NewCommentsHandler extends AbstractTrendChartHandler {

@Autowired
public NewCommentsHandler(EducationManager educationManager) {
super(educationManager);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
复制代码@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类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码@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中。

使用策略:

1
2
3
4
5
6
7
8
9
10
复制代码    /**
* 查看指数/新增课件数/新增点评数走势图
*
* @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逻辑实现:

1
2
3
复制代码    public List<TrendChartDataVo> queryTrendChartData(EducationQuery query) {
return trendChartHandlerContext.getInstance(query.getAreaRankingType()).handle(query);
}

可以看到,使用策略时只需要调用策略的handler方法即可,无需关注type,规避掉大量的if else代码。

工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码@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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
复制代码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:

未完待续

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%