有人問,我的程序太多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
保存在配置裏,再通過List
或Map
加載出來。
這樣,每當增加新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);
}
策略模式理解起來比較簡單,不再贅述,不同項目細節也可能不同,不如直接看代碼。
太多類會不會不太好
最後還有一個奇怪的問題,就是,怎麼看你加了那麼多的類,會不會不太好?
嗯,這個,不知道怎麼說,你去看看JDK或Spring裏的源碼吧,看看都長什麼樣,或者用代碼工具再掃掃自己的,對比一下。
好啦,重構適可而止,不然其他項目交不了貨啦!