背景
有一个SQL解析的方法,需要每种数据库类型都实现,入参出参和方法名均相同,根据数据库类型enum的不同值调用对应的方法。一开始是用switch/case方案写的,如下:
public class SqlStatementDetailParser {
/**
* sql : sql语句
* dbTypeEnum : 数据库枚举类型
**/
public static SqlParserDetail parser(String sql, DbTypeEnum dbTypeEnum) {
switch (dbTypeEnum) {
case MYSQL:
xxx
break;
case PGSQL:
xxx
break;
case SQLSERVER:
xxx
break;
default:
throw new UnsupportedOperationException("Unsupported DB Type");
}
return result;
}
}
上述代码中的xxx可能内容很长,如果有很多类型数据库,这个switch/case结构就会显得很冗长。而且如果case某种类别的处理逻辑太长,也会使得代码可读性很差。
如果是用手撸的工厂模式,很可能需要自行添加成员到工厂类的map里,可否利用spring的特性(注解+自动注入)减少代码量?
简化方案:巧妙利用spring的自动注入+策略模式+简单工厂模式
首先定义一个接口类,作为所有数据库类型解析SQL的父类。
public interface SqlDetailParser {
// 不同的数据库类型返回,用于初始化map使用
DbTypeEnum getDbType();
// 解析SQL方法
SqlParserDetailDTO parse(String sql);
}
接着实现该SqlDetailParser接口,以mysql和postgresql两种数据库类型为例:
@Component
public class MysqlStatementDetailParser implements SqlDetailParser {
@Override
public DbTypeEnum getDbType() {
return DbTypeEnum.MYSQL;
}
@Override
public SqlParserDetailDTO parse(String sql) {
xxx
}
}
@Component
public class PostgresqlStatementDetailParser implements SqlDetailParser {
@Override
public DbTypeEnum getDbType() {
return DbTypeEnum.PGSQL;
}
@Override
public SqlParserDetailDTO parse(String sql) {
xxx
}
}
构建工厂类,利用spring的注解,自动注入到list中。
@Service
public class SqlDetailParserFactory {
// 工厂模式,建立map
private final Map<DbTypeEnum, SqlDetailParser> sqlRelatedMap = Maps.newConcurrentMap();
// 因为SqlDetailParser的实现类添加了component注解,mysql和pg实现类会自动add到该list中
private final List<SqlDetailParser> sqlDetailParserList;
// 构造函数
public SqlDetailParserFactory(List<SqlDetailParser> sqlDetailParserList) {
this.sqlDetailParserList = sqlDetailParserList;
}
// 初始化阶段,初始化map
@PostConstruct
protected void init() {
this.sqlDetailParserList.forEach(this::register);
}
private void register(SqlDetailParser sqlDetailParser) {
sqlRelatedMap.put(sqlDetailParser.getDbType(), sqlDetailParser);
}
public SqlDetailParser createRelated(DbTypeEnum dbTypeEnum) {
SqlDetailParser sqlDetailParser = sqlRelatedMap.get(dbTypeEnum);
assertSqlParserAvailable(sqlDetailParser);
return sqlDetailParser;
}
private void assertSqlParserAvailable(SqlDetailParser sqlDetailParser) {
if (Objects.isNull(sqlDetailParser)) {
// 抛出异常
throw new xxx;
}
}
}
在上述的map初始化中,一般的工厂模式是将map定义为static,然后加静态块put元素进去。这里我们是利用了spring的component初始化时自动写入list的特性。
在调用时,只需用@Resource或@Autowired自动初始化factory类
@Resource
private SqlDetailParserFactory sqlDetailParserFactory;
// 获取不同数据库类型的parser类
SqlDetailParser sqlDetailParser = sqlDetailParserFactory.createRelated(dbTypeEnum);
便可大功告成。