重構 - Java程序太多if else怎麼辦

有人問,我的程序太多if else了,怎麼辦?

讓發過來看看,長這樣。

    public void execute(boolean ba, boolean bb, boolean bc) {
        if (ba && bb && bc) {
            response = callApiByChannelName(getChannelNameForThree(aChannel, bChannel, cChannel), xLoad, userInfo);
        } else if (ba && bb && !bc) {
            response = callApiByChannelName(getChannelNameForTwo(aChannel, bChannel), xLoad, userInfo);
        } else if (ba && !bb && bc) {
            response = callApiByChannelName(getChannelNameForTwo(aChannel, cChannel), xLoad, userInfo);
        } else if (!ba && bb && bc) {
            response = callApiByChannelName(getChannelNameForTwo(bChannel, cChannel), xLoad, userInfo);
        } else if (ba && !bb && !bc) {
            response = callApiOfChannelA(aChannel, xLoad, userInfo);
        } else if (!ba && bb && !bc) {
            response = callApiOfChannelB(bChannel, xLoad, userInfo);
        } else if (!ba && !bb && bc) {
            response = callApiOfChannelC(cChannel, xLoad, userInfo);
        }
    }

我說你不是看過設計模式嗎?
看過,可是還是不知道怎麼辦?

程序設計的原則簡單來說就兩點,首先是怎麼把無關的東西分開,然後是怎麼把共同的東西抽出來。

好吧,看你也聽不明白,幫你分析一下。

先理解業務邏輯再寫代碼

一看你這個條件就是一個排列組合嘛,你看getChannelNameForThree()這個方法是從三個中選一個是吧?

然後getChannelNameForTwo()是從兩個中選一個,剩下的不用選。

上面這四個callApiByChannelName()最終調用的實際上還是下面那三個方法,對吧?

而且我相信三個中選一個兩個中選一個的邏輯應該是一樣的,但是現在你的ifelse,卻被寫成了這個策略的一部分。

其實應該把它抽出來,先決定好調用哪個接口,然後再調用它,這樣就根本不需要這麼多ifelse了。

所以問題不在於ifelse,而是你沒有真正理解業務邏輯。

代碼中多幾個ifelse也並不是多嚴重的問題,真正的問題是,如果再加一個新成員來組合,該怎麼辦?

另一個問題就是,這個代碼再放幾天,估計連你自己也搞不清楚它到底是要做什麼的了。

說完他去檢查業務邏輯去了,等他檢查完,我的模擬重構代碼也基本上寫完了。

第一步優化結果如下,已經解決了ifelse多且難看的問題。

    public void step1() {
        int i = getTheRightChannel();
        if (i == 1) {
            response = callApiOfChannelA(aChannel, xLoad, userInfo);
        } else if (i == 2) {
            response = callApiOfChannelB(bChannel, xLoad, userInfo);
        } else {
            response = callApiOfChannelC(cChannel, xLoad, userInfo);
        }
    }

他檢查完之後的回覆在我意料之中,不過他卻興奮得像發現新大陸。我很理解這種心情。

使用工廠來創建對象

下一步要考慮的是,假如要再加一個Channel,要怎麼辦?

按照當前的程序,需要增加一個條件,再寫一個方法。如果方法比較複雜,程序必然越來越臃腫。

但觀察這幾個方法,只是調用的遠程接口的細節有所不同,所以可以抽象成一個統一的接口。

    public void step2() {
        int i = getTheRightChannel(  );
        Channel channel;
        if (i == 1) {
            channel = new ChannelA();
        } else if (i == 2) {
            channel = new ChannelB();
        } else {
            channel = new ChannelC();
        }
        response = channel.callApi(xLoad, userInfo);
    }

接着把Channel的創建放到一個統一的地方。這種方法被稱爲簡單工廠模式。

    public void step3() {
        int i = getTheRightChannel();
        ChannelSimpleFactory channelSimpleFactory = new ChannelSimpleFactory();
        Channel channel = channelSimpleFactory.createChannel(i);
        response = channel.callApi(xLoad, userInfo);
    }

現在若要再增加Channel,以上代碼已經無需修改了,只需新建一個Channel實現類,並在工廠裏增加創建該類的邏輯。

個人覺得,大多數的重構,到這一步就可以了。程序邏輯已相當清晰,擴展所需的修改也已非常簡單和可預見。

進一步的重構,需要考慮成本收益比,所以最好是等到下一次需求變更的時機。

修改只是擴展

當然,簡單工廠模式還是不符合OCP 原則(Open-Closed Principle) 。

Open for extention, closed for modification.

程序應該設計成容易擴展的,且不需要修改的。換句話說,最好的程序是,當你修改的時候,你只是在擴展。

針對當前這個業務,要達到這種程度,可以把不同種類的Channel保存在配置裏,再通過ListMap加載出來。

這樣,每當增加新Channel時,只需要寫新的Channel實現類,再把它添加到配置列表裏就可以。

以下代碼中Map的設置只是演示加載過程,實際上要放在配置裏。

    public void step4() {
        int i = getTheRightChannel();
        ChannelSimpleFactory channelSimpleFactory = new ChannelSimpleFactory();
        // should be in the configure
        Map map = new HashMap<Integer, Channel>();
        map.put(1, new ChannelA());
        map.put(2, new ChannelB());
        map.put(3, new ChannelC());
        channelSimpleFactory.setChannelMap(map);
        //
        Channel channel = channelSimpleFactory.createChannel(i);
        response = channel.callApi(xLoad, userInfo);
    }

策略變了怎麼辦

這樣修改之後,似乎很完美了,但其實還有一個地方,甚至比以上這步更值得優化。

那就是怎樣選出合適Channel的策略,getTheRightChannel(),這是第二可能變更需求的地方。

想想哪天老闆突然走過來說,發佈之前再幫忙改改這個吧,不要隨機選Channel了,給加個權重吧。

(此處省略一萬字……)

於是又要加配置,加方法,加ifelse,老路重走。

不過現在起碼還是可以修改的,看看最初的代碼,根本無從下手!

可以考慮加一個ChannelStrategy接口,當更換策略時,只需要新建一個子類ChannelWeightStrategy注入ChannelSimpleFactory就可以了。

    public void step5() {
        ChannelSimpleFactory channelFactory = new ChannelSimpleFactory();
        // or by configure
        ChannelStrategy strategy = new ChannelWeightStrategy();
        channelFactory.setStrategy(strategy);
        //
        Channel channel = channelFactory.newChannel();
        response = channel.callApi(xLoad, userInfo);
    }

策略模式理解起來比較簡單,不再贅述,不同項目細節也可能不同,不如直接看代碼。

https://github.com/prufeng/hellowork/tree/master/src/main/java/pan/rufeng/pattern/refactor/ifelsefactory

太多類會不會不太好

最後還有一個奇怪的問題,就是,怎麼看你加了那麼多的類,會不會不太好?

嗯,這個,不知道怎麼說,你去看看JDK或Spring裏的源碼吧,看看都長什麼樣,或者用代碼工具再掃掃自己的,對比一下。

好啦,重構適可而止,不然其他項目交不了貨啦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章