(设计模式)使用Java策略模式,优化了大量if...eles

1,708 阅读4分钟

前言

最近遇到个需求,需要对系统上传的各种文档文件进行管理,并支持根据文件名和文件内容来进行模糊检索。根据文件名来进行查询简单,但是要想根据文件内容来检索就有说法了。

最终落地的方案是:文件正常存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的情况,可以使用设计模式中的策略模式来对代码进行优化和规范

策略模式实现

首先,定义一个抽象策略接口

  1. 判断type是否需要当前类来解析

  2. 解析文件内容

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文件

那么只需要:

  1. 新增txt文件的解析类,实现Type接口

  2. 类中定义支持的文件后缀,重写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();
    }
}

end...