從6行到92行:If-Else是貧瘠的多態(tài)性
本文轉(zhuǎn)載自公眾號(hào)“讀芯術(shù)”(ID:AI_Discovery)。
筆者常??吹皆S多分支發(fā)生在枚舉或其他離散值上,當(dāng)某些開(kāi)發(fā)人員被要求不要使用if-then-else時(shí),甚至?xí)械綈阑?。if-else和switch當(dāng)然可以生成簡(jiǎn)潔的代碼,你的軟件不應(yīng)該由最少的行組成,也不必犧牲可讀性、可維護(hù)性或靈活性。
在if-then-else語(yǔ)句中使用枚舉的后果是什么呢?基于離散值的分支會(huì)使軟件難以更改。每個(gè)新功能都要求跟蹤分支發(fā)生的位置,并相應(yīng)地修改現(xiàn)有代碼。
這絕對(duì)不是我們想要的方式。這或許是使代碼正常工作的第一步,但隨著你不斷改進(jìn)代碼,switch和if-then-else肯定早已不復(fù)存在。
我必須要說(shuō),使用if-else和switch進(jìn)行分支的傳統(tǒng)方法已過(guò)時(shí)。它不可靠、不靈活。傳統(tǒng)方法中沒(méi)有面向?qū)ο蟮膬?nèi)容。但是它仍在蓬勃發(fā)展,因?yàn)閷W(xué)生們被迫認(rèn)為它是正確的,甚至是最佳實(shí)踐。代碼是有效的,但你可以做得更好。
設(shè)想一下這個(gè)問(wèn)題。假設(shè)出于某種原因,必須用一種方法來(lái)更新用戶(hù)。為了簡(jiǎn)單起見(jiàn),用戶(hù)只出于兩種理由需要在系統(tǒng)中被更新。
最初的一組要求
你可以在以下代碼段中實(shí)現(xiàn)這兩種簡(jiǎn)單的情況?;ㄒ稽c(diǎn)時(shí)間閱讀這段設(shè)計(jì)欠佳的代碼吧,許多高級(jí)開(kāi)發(fā)人員都把此當(dāng)作噩夢(mèng),它甚至被認(rèn)為是引發(fā)“十年怕井繩”的那條蛇。
是的,我看到過(guò)這樣瘋狂的野生代碼。這是一個(gè)非常幼稚的操作,它假定用戶(hù)永遠(yuǎn)沒(méi)有那么多理由進(jìn)行更改了。
無(wú)用if-else指令的可怕代碼示例
此代碼唯一好處是可以嘗試實(shí)現(xiàn)一種半CQS式的設(shè)計(jì)模式。如果你傾向于說(shuō)“那就應(yīng)該是一個(gè)switch”,那你應(yīng)該花點(diǎn)時(shí)間思考一下軟件開(kāi)發(fā)中到底什么是重要的。Switch對(duì)if-else來(lái)說(shuō)完全無(wú)關(guān)緊要。
你每時(shí)每刻都會(huì)受到新要求的打擊,誰(shuí)曾想到呢?你曾以為不會(huì)發(fā)生任何事情。對(duì)你的要求現(xiàn)在是這樣的:
你是否真的要通過(guò)添加其他枚舉值并附加兩個(gè)else-if語(yǔ)句來(lái)實(shí)現(xiàn)這兩個(gè)新原因下的用戶(hù)更新?如果決定走這條錯(cuò)路,結(jié)果就會(huì)是下面這樣。
復(fù)雜的、令人頭痛的分支
這種實(shí)現(xiàn)本質(zhì)上是貧瘠的多態(tài)性。除了不斷地添加額外的分支(這本身是一個(gè)值得懷疑的實(shí)踐)之外,每當(dāng)需要調(diào)試或執(zhí)行錯(cuò)誤修復(fù)時(shí),都會(huì)被完全無(wú)關(guān)的代碼包圍。
還有一個(gè)問(wèn)題。這個(gè)方法標(biāo)記正在欺騙我們,因?yàn)樗恢皇歉掠脩?hù)。它還根據(jù)更新原因選擇執(zhí)行哪種算法,甚至知道每種實(shí)現(xiàn)?,F(xiàn)在顯而易見(jiàn),這種方法負(fù)有大量責(zé)任。
我相信這個(gè)例子加深了一切關(guān)于if-else和switch的可怕印象。讓我們看看如何避免這種討厭的方法。
重構(gòu)為多態(tài)執(zhí)行非常容易。把基于分支的凌亂代碼重構(gòu)為內(nèi)聚的、簡(jiǎn)單的、吻合實(shí)際需求的類(lèi)。在有人說(shuō)害怕使用類(lèi)之前,筆者要先澄清一件事。實(shí)例化新類(lèi)的成本通??梢院雎圆挥?jì),在遇到瓶頸之前,請(qǐng)不要嘗試優(yōu)化代碼。
我們可以做得更好,可以編寫(xiě)可讀的、可維護(hù)的、靈活的代碼。通過(guò)用多態(tài)執(zhí)行代替?zhèn)鹘y(tǒng)的分支,類(lèi)與它管理的需求之間有了明確的聯(lián)系。具有明確職責(zé)的簡(jiǎn)單、高度凝聚力的類(lèi)易于維護(hù)。檢測(cè)和糾正缺陷變得輕而易舉。最重要的是,軟件可以輕松容納新功能,而無(wú)需修改現(xiàn)有類(lèi)。
讓我們開(kāi)始重構(gòu)。來(lái)看看不使用if-then-else或switch能做得多好。UpdateAsync(Reason,User)現(xiàn)在變得如此簡(jiǎn)單。
簡(jiǎn)化的UpdateAsync方法實(shí)現(xiàn)
請(qǐng)注意,你現(xiàn)在使用的是接口參數(shù)而不是枚舉。現(xiàn)在,該方法委托了知道如何對(duì)特定對(duì)象執(zhí)行更新的職責(zé)。IUpdateReason的具體實(shí)現(xiàn)如下所示,構(gòu)造函數(shù)參數(shù)和方法實(shí)現(xiàn)的細(xì)節(jié)不多做贅述。
UpdateReason接口及其具體實(shí)現(xiàn)
每個(gè)類(lèi)都完全符合其管理的要求。與過(guò)時(shí)方法相比,調(diào)試,修復(fù)錯(cuò)誤和測(cè)試現(xiàn)在要容易多。在這種情況下,任何新要求都會(huì)產(chǎn)生一個(gè)專(zhuān)門(mén)的類(lèi)。
我們可以輕松地停在這里,結(jié)束一天的工作。你重構(gòu)了繁瑣的分支,并將其替換為多態(tài)。你的代碼現(xiàn)在是面向?qū)ο蟮模⑶乙子诰S護(hù)。但也可以選擇進(jìn)行最后一步。UpdateAsync(Reason,User)現(xiàn)在有些多余。為了解決這個(gè)問(wèn)題,我們不再進(jìn)行重構(gòu)——我們正在重新設(shè)計(jì)系統(tǒng)的各個(gè)部分。
在這種情況下,創(chuàng)建命令對(duì)象和命令處理程序是有意義的。它將簡(jiǎn)化調(diào)用代碼,因?yàn)樗徽{(diào)度了諸如UpdateUserAddress之類(lèi)的命令,并且將調(diào)用相應(yīng)的處理程序的操作。
圖源:unsplash
在發(fā)現(xiàn)更合適的多態(tài)方法之前,傳統(tǒng)的分支通常是學(xué)生的工具。但毫無(wú)疑問(wèn),if-then-else和switch會(huì)使代碼難以閱讀、維護(hù)和調(diào)整。下次要使用傳統(tǒng)的多路分支來(lái)實(shí)現(xiàn)功能時(shí),請(qǐng)花點(diǎn)時(shí)間分析如何利用多態(tài)性和現(xiàn)代方法,這樣可以讓你事半功倍。