设计模式之策略模式应用

1.概述

本篇文章主要介绍策略模式在SpringBoot框架中如何使用。是一篇策略模式的应用实践的文章,需要对设计模式的理论又一定的了解。

1.1 策略模式简述

策略模式包含以下3个核心角色:
- 环境(Context):定义了使用算法的环境,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入、简单工厂等方式来获取具体策略对象。
- 抽象策略(Abstract Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。
- 具体策略(Concrete Strategy):实现了抽象策略定义的接口或抽象类,包含了具体的算法实现。

2.实现

作为程序员,无论是在日常开发、学习中,肯定会学习了解各种的设计模式。在开发中我们如果根据需求选择合适的设计模式,使得我们写出的代码更加优雅,且具被拓展性,可读性,是需要我们时刻思考的。

收到项目现场转来的一个紧急需求,需要解析一批csv格式的文件入库,每个csv文件都对应一套数据,后续跟能还会增加新的数据。看到之前的代码就是一阵无语…,一坨 “if-else” 堆砌代码。

2.1构建思路

根据上面的需求不同类型数据文件采用不同的处理方法,对于有经验的程序员,肯定第一个想到的肯定是策略模式。若对设计模式不是很熟,也可根据需求对照设计模式定义挑选。

- 定义可识别常量:这里根据不同的文件名标识策略类型,使用文件名调用对应的策略实例。
- 定义策略接口:每个文件定义一种解析实现,实现对应的解析逻辑,并提取抽接口,定义为对外统一访问入口。
- Context设计:通过Map来存储策略定义数据,调用者调用是通过策略标识来获取对应策略实例。
在不使用springboot时,需要将策略标识与对应实例直接在代码中写死,这种方式不利于拓展,每次增加策略后都要修改Map。所以强烈建议使用springboot,通过注解方式实现策略的自动注册。新增策略后springboot启动时会扫描策略并对其进行注册,不需要修改旧代码。

2.2代码实现

下面我们首先定义策略接口及策略实例,使用springboot框架时只需要在策略实例上增加Component注解,即可将实例注入到Map中。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 策略接口
*/
public interface CSVParser {
public static final String cvsSplitBy = "\\|";
/**
*
* @param br
* @param fileType
*/
void parseCsv(BufferedReader br, String fileType) throws IOException;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 策略实现 city_xxxxxx.csv文件
*/
@Component("City")
public class CityParser implements CSVParser{
private Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public ParseResult parseCsv(BufferedReader br, String fileType) throws IOException {
logger.info("City");
//这里实现csv格式解析以及后续处理,此处省略...
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 策略实现 region_xxxxxx.csv文件
*/
@Component("Region")
public class RegionParser implements CSVParser{
private Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public void parseCsv(BufferedReader br, String fileType) throws IOException {
logger.info("Region");
//这里实现csv格式解析以及后续处理,此处省略...
}
}

其次,编写策略选择器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 解析策略选择器
*/
@Component
public class ParseStrategyContext {
//这里springboot会扫描已定义的策略,并注册到selectParser中
@Resource
private final Map<String,CSVParser> selectParser=new ConcurrentHashMap();

/**
* 根据资源类型选择对应的解析策略
* @param type 资源类型
* @return
*/
public CSVParser select(String type){
return selectParser.get(type);
}
}

2.3拓展

当需求发生变化,需要增加新类型文件时,只需要新增一个策略实例即可,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 策略实现 room_xxxxxx.csv文件
*/
@Component("Room")
public class RoomParser implements CSVParser{
private Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public void parseCsv(BufferedReader br, String fileType) throws IOException {
logger.info("Room");
//这里实现csv格式解析以及后续处理,此处省略...
}
}

2.4调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

@SpringBootTest
public class NewCsvParseTest {

String filePath = "d://temp";

@Autowired
ParseStrategySelector strategySelector;

@Test
void parseCsv() {
File file = new File(filePath);
File[] childFiles = file.listFiles();
BufferedReader br;
for (File childFile : childFiles) {
List<String> fileNameList = Arrays.asList(childFile.getName().split("_"));
String fileType=fileNameList.get(0);
br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "UTF-8"));
//根据文件类型,调用不同的策略实例
CSVParser parser = strategySelector.select(fileType);
ParseResult parseResult = parser.parseCsv(br, fileType);
}
}
}

3.总结

通过使用策略模式模式,大大减少了代码中的 if-else 使用,代码更加优雅,便于扩展。其次,springboot框架能够更加高效的完成开发任务。