CTO:别再写一摞if-else了,再写开除!
The following article is from 胖滚猪学编程 Author Liuyanling
代码洁癖狂们!看到一个类中有几十个 if-else 是不是很抓狂?设计模式学了用不上吗?面试的时候问你,你只能回答最简单的单例模式,问你有没有用过反射之类的高级特性,回答也是否吗?
1
那个坑货
某日,码农胖滚猪接到上级一个需求,这个需求牛逼了,一站式智能报表查询平台,支持 MySQL、Pgxl、TiDB、Hive、Presto、Mongo 等众多数据源,想要啥数据都能通通给你查出来展示,对于业务人员数据分析有重大意义!
领导胖滚熊也对胖滚猪的效率表示了肯定。可是好景不长,第三天,领导闲着没事,准备做一下 Code Review,可把胖滚熊惊呆了,一个类里面有近 30 个 if-else 代码,我滴个妈呀,这可让代码洁癖狂崩溃了。
// 检验入参合法性
Boolean check = false;
if(DataSourceEnum.hive.equals(dataSource)){
check = checkHiveParams(params);
} else if(DataSourceEnum.tidb.equals(dataSource)){
check = checkTidbParams(params);
} else if(DataSourceEnum.mysql.equals(dataSource)){
check = checkMysqlParams(params);
} // else if ....... 省略pgxl、presto等
if(check){
if(DataSourceEnum.hive.equals(dataSource)){
list = queryHive(params);
} else if(DataSourceEnum.tidb.equals(dataSource)){
list = queryTidb(params);
} else if(DataSourceEnum.mysql.equals(dataSource)){
list = queryMysql(params);
} // else if ....... 省略pgxl、presto等
}
//记录日志
log.info("用户={} 查询数据源={} 结果size={}",params.getUserName(),params.getDataSource(),list.size());
2
模板模式来救场
校验参数合法性
查询
记录日志
这不就是说模板一样、只不过具体细节不一样,没错吧?让我们来看看设计模式中模板方法模式的定义吧:
模板方法模式:定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。通俗的讲,就是将子类相同的方法,都放到其抽象父类中。
至于检验参数和查询,这两个方法各不相同,因此需要置为抽象方法,由子类去重写。
public abstract class AbstractDataSourceProcesser <T extends QueryInputDomain> {
public List<HashMap> query(T params){
List<HashMap> list = new ArrayList<>();
//检验参数合法性 不同的引擎sql校验逻辑不一样
Boolean b = checkParam(params);
if(b){
//查询
list = queryData(params);
}
//记录日志
log.info("用户={} 查询数据源={} 结果size={}",params.getUserName(),params.getDataSource(),list.size());
return list;
}
//抽象方法 由子类来实现特定逻辑
abstract Boolean checkParam(T params);
abstract List<HashMap> queryData(T params);
}
但是他们也有公共的参数,比如用户名。因此为了不重复冗余,更好的利用公共资源及规范入参,在泛型的设计上,我们可以有一个泛型上限,<T extends QueryInputDomain>:
public class QueryInputDomain<T> {
public String userName;//查询用户名
public String dataSource;//查询数据源 比如mysql\tidb等
public T params;//特定的参数 不同的数据源参数一般不一样
}
public class MysqlQueryInput extends QueryInputDomain{
private String database;//数据库
public String sql;//sql
}
下面以 MySQL 数据源为例,其他数据源也都一样的套路:
@Component("dataSourceProcessor#mysql")
public class MysqlProcesser extends AbstractDataSourceProcesser<MysqlQueryInput>{
@Override
public Boolean checkParam(MysqlQueryInput params) {
System.out.println("检验mysql参数是否准确");
return true;
}
@Override
public List<HashMap> queryData(MysqlQueryInput params) {
List<HashMap> list = new ArrayList<>();
System.out.println("开始查询mysql数据");
return list;
}
}
3
工厂模式来救场
但是模板模式还是没有完全解决胖滚猪的 if-else,因为需要根据传进来的 dataSource 参数,判断由哪个 service 来实现查询逻辑,现在是这么写的:
if(DataSourceEnum.hive.equals(dataSource)){
list = queryHive(params);
} else if(DataSourceEnum.tidb.equals(dataSource)){
list = queryTidb(params);
}
以手机制造业为例。我们知道有苹果手机、小米手机等等,每种品牌的手机制造方法必然不相同,我们可以先定义好一个手机标准接口,这个接口有 make() 方法,然后不同型号的手机都继承这个接口:
#Phone类:手机标准规范类(AbstractProduct)
public interface Phone {
void make();
}
#MiPhone类:制造小米手机(Product1)
public class MiPhone implements Phone {
public MiPhone() {
this.make();
}
@Override
public void make() {
System.out.println("make xiaomi phone!");
}
}
#IPhone类:制造苹果手机(Product2)
public class IPhone implements Phone {
public IPhone() {
this.make();
}
@Override
public void make() {
System.out.println("make iphone!");
}
}
其实也很简单,简单工厂模式(还有抽象工厂模式和工厂方法模式,有兴趣可以了解下)是这么实现的:
#PhoneFactory类:手机代工厂(Factory)
public class PhoneFactory {
public Phone makePhone(String phoneType) {
if(phoneType.equalsIgnoreCase("MiPhone")){
return new MiPhone();
}
else if(phoneType.equalsIgnoreCase("iPhone")) {
return new IPhone();
}
}
}
你会发现其实也不过是 if-else,但是把 if-else 抽到一个工厂类,由工厂类统一创建对象,对我们的业务代码无入侵,不管是维护还是美观上都会好很多。
该注解我想应该不用多解释了吧,重点是:我们可以把不同数据源都搞成类似的 bean name,形如 dataSourceProcessor#数据源名称,如下两段代码:
@Component("dataSourceProcessor#mysql")
public class MysqlProcesser extends AbstractDataSourceProcesser<MysqlQueryInput>{
@Component("dataSourceProcessor#tidb")
public class TidbProcesser extends AbstractDataSourceProcesser<TidbQueryInput>{
Key 是 Bean 的名称,而 Value 则是对应的 Bean:
@Service
public class QueryDataServiceImpl implements QueryDataService {
@Resource
public Map<String, AbstractDataSourceProcesser> dataSourceProcesserMap;
public static String beanPrefix = "dataSourceProcessor#";
@Override
public List<HashMap> queryData(QueryInputDomain domain) {
AbstractDataSourceProcesser dataSourceProcesser = dataSourceProcesserMap.get(beanPrefix + domain.getDataSource());
//省略query代码
}
}
根据 key 可以直接从 dataSourceProcesserMap 中获取到 TidbProcesser:
4
反射来救场
public static String classPrefix = "com.lyl.java.advance.service.";
AbstractDataSourceProcesser sourceGenerator =
(AbstractDataSourceProcesser) Class.forName
(classPrefix+DataSourceEnum.getClasszByCode(domain.getDataSource()))
.newInstance();
因此可以用到枚举类,去定义好不同数据源的类名:
public enum DataSourceEnum {
mysql("mysql", "MysqlProcesser"),
tidb("tidb", "TidbProcesser");
private String code;
private String classz;
5
总结
作者:Liuyanling
编辑:陶家龙
出处:转载自微信公众号胖滚猪学编程(ID:bdstar_lyl)
精彩文章推荐: