別再寫一摞if-else了!再寫開除!兩種設計模式帶你消滅它!

代碼潔癖狂們!看到一個類中有幾十個if-else是否是很抓狂?
設計模式學了用不上嗎?面試的時候問你,你只能回答最簡單的單例模式,問你有沒有用過反射之類的高級特性,回答也是否嗎?
此次就讓設計模式(模板方法模式+工廠模式)和反射助你消滅if-else!
真的是開發中超超超超超超有用的乾貨啊!前端

那個坑貨

某日,碼農胖滾豬接到上級一個需求,這個需求牛逼了,一站式智能報表查詢平臺,支持mysql、pgxl、tidb、hive、presto、mongo等衆多數據源,想要啥數據都能統統給你查出來展現,對於業務人員數據分析有重大意義!
imagejava

雖然各個數據源的參數校驗、查詢引擎和查詢邏輯都不同,可是胖滾豬對這些框架都很熟悉,這個難不倒她,她只花了一天時間就都寫完了。mysql

領導胖滾熊也對胖滾豬的效率表示了確定。但是好景不長,第三天,領導閒着沒事,準備作一下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());

image

模板模式來救場

首先咱們來分析下,不論是什麼數據源,算法結構(流程)都是同樣的,一、校驗參數合法性 二、查詢 三、記錄日誌。這不就是說模板同樣、只不過具體細節不同,沒錯吧?算法

讓咱們來看看設計模式中模板方法模式的定義吧:spring

模板方法模式:定義一個操做中的算法的框架,而將一些步驟延遲到子類中. 使得子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟。通俗的講,就是將子類相同的方法, 都放到其抽象父類中。sql

咱們這需求不就和模板方法模式差很少嗎?所以咱們能夠把模板抽到父類(抽象類)中。至於特定的步驟實現不同,這些特殊步驟,由子類去重寫就行了。數據庫

廢話很少說了,咱們先把父類模板寫好吧,徹底同樣的邏輯是記錄日誌,這步在模板寫死就好。至於檢驗參數和查詢,這兩個方法各不相同,所以須要置爲抽象方法,由子類去重寫。編程

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這個玩意。叫泛型,由於不一樣數據源的入參不同,因此咱們使用泛型。可是他們也有公共的參數,好比用戶名。所以爲了避免重複冗餘,更好的利用公共資源,在泛型的設計上,咱們能夠有一個泛型上限,<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
}

接下來就輪到子類出場了,經過上面的分析,其實也很簡單了,不過是繼承父類,重寫checkParam()和queryData()方法,下面以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;
    }
}

這樣一來,全部的數據源,都自成一體,擁有一個只屬於本身的類,後續要擴展數據源、或者要修改某個數據源的邏輯,都很是方便和清晰了。

說實話,模板方法模式太簡單了,抽象類這東西也太基礎廣泛了,通常應屆生都會知道的。可是對於初入職場的新人來講,還真不太能果斷應用在實際生產中。所以提醒各位:必定要有一個抽象思惟,避免代碼冗餘重複。

另外,要再囉嗦幾句,即便工做有幾年的工程師也很容易犯一個錯誤。就是把思惟侷限在今天的需求,好比老闆一開始只給你一個mysql數據源查詢的需求,壓根沒有if-else,可能你就不會放在心上,直接在一個類中寫死,不會考慮到後續的擴展。直到後面愈來愈多的新需求,你才恍然大悟,要所有重構一番,這樣浪費本身的時間了。所以提醒各位:作需求不要侷限於今天,要考慮到將來。 從一開始就作到高擴展性,後續需求變動和維護就很是爽了。

原創聲明:本文爲【胖滾豬學編程】原創博文,轉載請註明出處。以漫畫形式讓編程生動有趣!原創不易,求關注!

工廠模式來救場

可是模板模式仍是沒有徹底解決胖滾豬的if-else,由於須要根據傳進來的dataSource參數,判斷由哪一個service來實現查詢邏輯,如今是這麼寫的:

if(DataSourceEnum.hive.equals(dataSource)){
        list = queryHive(params);
    } else if(DataSourceEnum.tidb.equals(dataSource)){
        list = queryTidb(params);
    }

那麼這種if-else應該怎麼去幹掉呢?我想先跟你講講工廠模式的那些故事。

工廠模式:工廠方法模式是一種建立對象的模式,它被普遍應用在jdk中以及Spring和Struts框架中。它將建立對象的工做轉移到了工廠類。

爲了呼應一下工廠兩字,我特地舉一個代工廠的例子讓你理解,這樣你應該會有更深入的印象。

以手機制造業爲例。咱們知道有蘋果手機、小米手機等等,每種品牌的手機制造方法必然不相同,咱們能夠先定義好一個手機標準接口,這個接口有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抽到一個工廠類,由工廠類統一建立對象,對咱們的業務代碼無入侵,不論是維護仍是美觀上都會好不少。
image

首先,咱們應該在每一個特定的dataSourceProcessor(數據源執行器),好比MysqlProcesser、TidbProcesser中添加spring容器註解@Component。該註解我想應該不用多解釋了吧~重點是:咱們能夠把不一樣數據源都搞成相似的bean name,形如dataSourceProcessor#數據源名稱,以下兩段代碼:

@Component("dataSourceProcessor#mysql")
public class MysqlProcesser extends AbstractDataSourceProcesser<MysqlQueryInput>{
@Component("dataSourceProcessor#tidb")
public class TidbProcesser extends AbstractDataSourceProcesser<TidbQueryInput>{

這樣有什麼好處呢?我能夠利用Spring幫咱們一次性加載出全部繼承於AbstractDataSourceProcesser的Bean ,形如Map<String, AbstractDataSourceProcesser>,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代碼
    }
}

可能你仍是不太理解,咱們直接看一下運行效果:

一、dataSourceProcesserMap內容以下所示,存儲了全部數據源Bean,Key是Bean的名稱、而Value則是對應的Bean:
image

二、我只須要經過key(即前綴+數據源名稱=beanName),就能匹配到對應的執行器了。好比當參數dataSource爲tidb的時候,key爲dataSourceProcessor#tidb,根據key能夠直接從dataSourceProcesserMap中獲取到TidbProcesser

image

image

image

public static String classPrefix = "com.lyl.java.advance.service.";

AbstractDataSourceProcesser sourceGenerator = 
(AbstractDataSourceProcesser) Class.forName
(classPrefix+DataSourceEnum.getClasszByCode(domain.getDataSource()))
.newInstance();

須要注意的是,該種方法是經過className來獲取到類的實例,而前端傳參確定是不會傳className過來的。所以能夠用到枚舉類,去定義好不一樣數據源的類名:

public enum DataSourceEnum {
    mysql("mysql", "MysqlProcesser"),
    tidb("tidb", "TidbProcesser");
    private String code;
    private String classz;

原創聲明:本文爲【胖滾豬學編程】原創博文,轉載請註明出處。以漫畫形式讓編程生動有趣!原創不易,求關注!

總結

有些童鞋總以爲設計模式用不上,由於平時寫代碼除了CRUD仍是CRUD,面試的時候問你設計模式,你只能回答最簡單的單例模式,問你有沒有用過反射之類的高級特性,回答也是否。

其實否則,JAVA這23種設計模式,每個都是經典。今天咱們就用模板方法模式+工廠模式(或者反射)解決了讓人崩潰的if-else。後續對於設計模式的學習,也應該多去實踐,從真實的項目中找到用武之地,你纔算真正把知識佔爲己有了。

本篇文章的內容和技術點雖然很簡單,但旨在告訴你們應該要有一個很好的代碼抽象思惟。杜絕在代碼中出現一大摞if-else或者其餘爛代碼。

即便你有很好的代碼抽象思惟,作需求開發的時候,也不要侷限於當下,只考慮如今,要多想一想將來的擴展性。

就像你談戀愛同樣,只考慮當下的是渣男,考慮到將來的,纔算是一個負責任的人

"願世界沒有渣男"

原創聲明:本文爲【胖滾豬學編程】原創博文,轉載請註明出處。以漫畫形式讓編程生動有趣!原創不易,求關注!

本文來源於公衆號:【胖滾豬學編程】。一枚集顏值與才華於一身,不算聰明卻足夠努力的女程序媛。用漫畫形式讓編程so easy and interesting!求關注!

相關文章
相關標籤/搜索