前言
最近遇到个需求,需要对系统上传的各种文档文件进行管理,并支持根据文件名和文件内容来进行模糊检索。根据文件名来进行查询简单,但是要想根据文件内容来检索就有说法了。
最终落地的方案是:文件正常存oos服务器,解析文件内容为字符串并通过ElasticSearch来支撑文件名和文件内容的检索功能(分词、相关度匹配)。
但是今天不谈这个,今天来谈一个设计模式的问题。
问题出现在解析文件内容这一块,我们都知道,常用的文档文件并不止一种格式,如word,excel,txt....
而这些不同类型的文件需要使用不同的文件解析方法,目前代码如下:
//校验文件类型
if(!fileType.equals("doc")
&& !fileType.equals("docx")
&& !fileType.equals("xls")
&& !fileType.equals("xlsx")) {
return "file type error";
}
//根据文件类型解析文件
String contentFromUrl;
if(fileType.equals("doc") || fileType.equals("docx")) {
//读取word里面文字内容
contentFromUrl = String.join("", WordUtils.readWord(fileType, url));
}else {
//读取excel里面文字内容
contentFromUrl = ExcelUtils.getContentFromUrl(url);
}
//将contentFromUrl存es
目前支持的文件类型有word和excel,可以预见的是,未来随着业务的拓展,需要支持的文件类型会越来越多。同时会导致以上代码越来越难维护(大量的if...else)。
针对这种需要大量if...else的情况,可以使用设计模式中的策略模式来对代码进行优化和规范
策略模式实现
首先,定义一个抽象策略接口
-
判断type是否需要当前类来解析
-
解析文件内容
public interface Type {
/**
* 当前type是否当前类型
* @param type String
* @return true or false
*/
boolean isMe(String type);
/**
* 解析当前文件为供ES分词的字符串
* @param fileType fileType
* @param url 文件url
* @return String
*/
String getFileContentForEsStr(String fileType,String url);
}
接着定义抽象接口的两个实现
-
这里定义一个myTypeList是为了判断type是否需要当前类来解析(对应if里面的条件),比如说word文件有.doc和.docx类型,这俩类型使用的都是word文件的解析方法。那么维护一个list,通过isMe()方法在实现if里面的判断
-
接着实现当前解析类的解析方法
/**
* 解析Doc文件为供ES分词的字符串
*/
public class DocType implements Type{
private static final List<String> myTypeList = new ArrayList<String>(){{//文件后缀
add("doc");
add("docx");
}};
@Override
public boolean isMe(String type) {
return myTypeList.contains(type);
}
@Override
public String getFileContentForEsStr(String fileType,String url) {
return String.join("", WordUtils.readWord(fileType, url));
}
}
/**
* 解析Excel文件为供ES分词的字符串
*/
public class ExcelTpe implements Type{
private static final List<String> myTypeList = new ArrayList<String>(){{//文件后缀
add("xls");
add("xlsx");
}};
@Override
public boolean isMe(String type) {
return myTypeList.contains(type);
}
@Override
public String getFileContentForEsStr(String fileType, String url) {
return ExcelUtils.getContentFromUrl(url);
}
}
最后定义一个策略容器
-
myTypeList为Type接口所有实现类的集合,这里通过注入的方式来使用
-
判断type是否支持的方法
-
根据文件类型调用对应的解析方法
@Component
public class TypeContext {
@Resource
private List<Type> myTypeList;
/**
* type是否支持
* @param type 文件类型
* @return 支持为true,不支持为false
*/
public boolean support(String type){
for (Type item : myTypeList) {
if (item.isMe(type)){
return true;
}
}
return false;
}
/**
* 根据文件类型调用对应的解析方法
* @param type 文件类型
* @return String
*/
public String getFileContentForEsStr(String type,String url){
for (Type item : myTypeList) {
if (item.isMe(type)){
return item.getFileContentForEsStr(type,url);
}
}
return null;
}
}
优化完成
使用策略模式之后,原来的业务代码就变成了:
//注入策略容器
@Autowired
private TypeContext typeContext;
//校验文件类型
if(!typeContext.support(fileType)) {
return "file type error";
}
//根据文件类型解析文件
String contentFromUrl = typeContext.getFileContentForEsStr(fileType,url);
//将contentFromUrl存es
如果后续需要支持对其它格式文件的解析,如txt文件
那么只需要:
-
新增txt文件的解析类,实现Type接口
-
类中定义支持的文件后缀,重写isMe()和具体解析方法getFileContentForEsStr()
而业务代码完全不需要修改。
其它说明
1、对于使用单个字符串来判断的大量if...else,如:
if(a == "one"){
//one...
}else if(a == "two"){
//two...
}else ...
这种情况使用策略模式来优化时,可以在策略容器中使用Map+枚举类来维护,可以优化解析实现类的myTypeList以及isMe()方法
2、在策略容器里面注入的myTypeList
,在SpringBoot中可以使用配置类来初始化,如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* type配置类,在类中注册支持的Type
* 业务类使用@Autowired 来使用myTypeList bean
*/
@Configuration
public class MyTypeConfig {
@Bean
public List<Type> myTypeList(){
ArrayList<Type> myTypeList = new ArrayList<>();
myTypeList.add(myDocType());
myTypeList.add(myExcelTpe());
System.out.println("myTypeList初始化完成");
return myTypeList;
}
@Bean
public DocType myDocType(){
return new DocType();
}
@Bean
public ExcelTpe myExcelTpe(){
return new ExcelTpe();
}
}