新聞中心
圖片來自 Pexels

創(chuàng)新互聯(lián)是一家集網(wǎng)站建設,梅江企業(yè)網(wǎng)站建設,梅江品牌網(wǎng)站建設,網(wǎng)站定制,梅江網(wǎng)站建設報價,網(wǎng)絡營銷,網(wǎng)絡優(yōu)化,梅江網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學習、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。
如何優(yōu)化這些 if-else 呢?本文分享一種設計模式:責任樹模式。
通過將責任鏈與策略模式融合,成為一種廣義的責任鏈模式,不僅可以完成任務的逐級委托,也可以在任一級選擇不同的下游策略進行處理,并將責任樹模式抽象出一個通用的框架。
捫心自問,你在寫業(yè)務代碼時是不是也習慣狂堆 if-else 呢?
問題背景
最近開發(fā)了一個需求,該接口需要根據(jù) p1、p2、p3、version 多個入?yún)⒌牟煌M合按照其對應的業(yè)務策略給出結果數(shù)據(jù)。
由于該接口已經開發(fā)了三期了,每次開發(fā)新一期的需求時為了兼容老的業(yè)務邏輯,大家都傾向于不刪不改只新增。
因此這塊代碼已經產生了一些“壞味道”,函數(shù)入口通過不斷添加“衛(wèi)語句”判斷 version 的方式跳轉到新一期的業(yè)務邏輯方法中。
而每一期的業(yè)務邏輯也是通過 p1、p2、p3 的 if-else 組合形成不同的分支邏輯。
這已經是我簡化后的表述,總之剛開始對于我這個新同學來說,梳理這塊業(yè)務代碼著實花了一些功夫。
而且,這塊邏輯相當于是一個業(yè)務上的通用能力,未來一定還會有五期、六期、N 期的需求進來,入?yún)⒌娜≈狄矔粩嗤卣梗虼艘袁F(xiàn)有方式膨脹下去只會“壞味道”會越來越重。
總結一下,當前場景面臨的問題是:
- 如何解決接口升級,在保證兼容老版本的情況下輕松開發(fā)新版本業(yè)務邏輯?
- 如何根據(jù)入?yún)?p1、p2、p3 等的不同組合進行策略定位?
解決思路
在思考解決方案時,很容易想到兩種可以優(yōu)化類似場景的設計模式:責任鏈模式和策略模式。
責任鏈模式
責任鏈模式是實現(xiàn)了類似“流水線”結構的逐級處理,通常是一條鏈式結構,將“抽象處理者”的不同實現(xiàn)串聯(lián)起來。
如果當前節(jié)點能夠處理任務則直接處理掉,如果無法處理則委托給責任鏈的下一個節(jié)點,如此往復直到有節(jié)點可以處理這個任務。
我們可以通過責任鏈模式完成對不同 version 業(yè)務邏輯隔離的處理,比如節(jié)點 1 處理 version=1 的請求,節(jié)點 2 處理 version=2 的請求等等。
但問題在于我們遇到的場景還需要根據(jù)一定策略,路由到不同的下游節(jié)點進行處理。這就是策略模式擅長解決的問題了。
策略模式
策略模式的目的是將算法的使用與定義解耦,能夠實現(xiàn)根據(jù)規(guī)則路由到不同策略類進行處理。
我們可以通過策略模式解決根據(jù)不同參數(shù)組合執(zhí)行不同業(yè)務邏輯的場景。但是我們的場景僅僅通過一層策略路由無法滿足任務處理需求。請求的分層處理又是責任鏈模式所擅長的了。
可以看到,兩種設計模式都不完全符合目前這個場景:責任鏈模式可以實現(xiàn)逐級委托,但每一級又不能像策略模式那樣路由到不同的處理者上;策略模式通常只有一層路由,不易實現(xiàn)多個參數(shù)的策略組合。
因此我們自然而然地可以想到:是不是可以將兩種模式結合起來?
廣義責任鏈模式:責任樹模式
將責任鏈與策略模式融合,即成為了一種廣義的責任鏈模式,我簡稱為“責任樹模式”。
這種模式不僅可以完成任務的逐級委托,也可以在任一級選擇不同的下游策略進行處理。
那么問題來了,如何通過責任樹模式解決前面我們遇到的問題呢?
首先看如何解決第一個問題,新老接口的隔離和兼容:可以將接口每個版本的邏輯作為一個責任樹上第一層的不同實現(xiàn),如分別對應上圖中的 Strategy1、Strategy2、Strategy3 節(jié)點。
這樣在接口入口,就首先把策略路由到不同的分支上去。如果沒有節(jié)點命中,則不再向下游委托直接返回錯誤。
然后第二個問題,參數(shù)的組合定位到不同的策略實現(xiàn)上:同樣的思路,一個參數(shù)對應責任樹上的一層的路由,將該參數(shù)的不同取值路由到下一層的不同實現(xiàn)即可,這樣逐級委托,后面新增入?yún)⒌拿杜e值、甚至再拓展新的入?yún)⒍伎梢苑浅7奖愕剡M行拓展。
優(yōu)化收益
將這塊業(yè)務通過“責任樹模式”重構之后,可以收獲以下幾個收益點:
- 后續(xù)迭代人力成本降低。
- 代碼結構更清晰,可維護性提升:沒有了各種“衛(wèi)語句”的跳轉 & 維護性巨差的巨型方法,函數(shù)可以收斂在理想的 50 行內。
- 后續(xù)新增需求修改代碼不易出錯:策略間隔離,不需要完整看一遍大函數(shù)理清邏輯再修改,只需要無腦添加一條路由 + 新的策略實現(xiàn)方法即可。
- 問題易定位:同樣由于策略間隔離,調試時可以直接定位到指定策略的業(yè)務邏輯代碼,不需要逐句排查。
相信有開發(fā)經驗的同學應該都有體會,即使是自己寫過的代碼,一陣子不看也會忘掉,等到再有修改時,還要順著代碼理一遍邏輯,如果文檔、注釋沒寫好,那就更加酸爽了。因此,將巨型函數(shù)拆分解耦非常重要。
抽象框架
雖然通過“責任樹模式”解決了我這個需求開發(fā)中遇到的問題,但是類似的問題還是普遍存在的。
本著助(shǎo)人(zào)為(lún)樂(zi)的精神,我更進一步,將責任樹模式抽象出一個通用的框架,方便大家在遇到類似問題時快速“種樹”。
這個框架由一個 Router 和 Handler 組成:
- Router 是一個抽象類,負責定義如何路由到下游的多個子節(jié)點。
- Handler 是接口,負責實現(xiàn)每個節(jié)點的業(yè)務邏輯。
我們可以非常方便地通過 Router 和 Handler 的組合拼裝成整棵樹的結構。
從圖中我們可以看出以下幾個要點:
- 除了根節(jié)點(入口)外,每個節(jié)點都實現(xiàn)了 Handler 接口。根節(jié)點只繼承 Router 抽象類。
- 所有葉子節(jié)點只實現(xiàn) Handler 接口而無需繼承 Router 抽象類(無需再向下委托)。
- 除了根節(jié)點和葉子節(jié)點外的其他節(jié)點,都是上一層的 Handler,同時是下一層的 Router。
那么我們話不多說,先看下框架代碼。
①AbstractStrategyRouter 抽象類:
- /**
- * 通用的“策略樹“框架,通過樹形結構實現(xiàn)分發(fā)與委托,每層通過指定的參數(shù)進行向下分發(fā)委托,直到達到最終的執(zhí)行者。
- * 該框架包含兩個類:{@code StrategyHandler} 和 {@code AbstractStrategyRouter}
- * 其中:通過實現(xiàn) {@code AbstractStrategyRouter} 抽象類完成對策略的分發(fā),
- * 實現(xiàn) {@code StrategyHandler} 接口來對策略進行實現(xiàn)。
- * 像是第二層 A、B 這樣的節(jié)點,既是 Root 節(jié)點的策略實現(xiàn)者也是策略A1、A2、B1、B2 的分發(fā)者,這樣的節(jié)點只需要
- * 同時繼承 {@code StrategyHandler} 和實現(xiàn) {@code AbstractStrategyRouter} 接口就可以了。
- *
- *
- * +---------+
- * | Root | ----------- 第 1 層策略入口
- * +---------+
- * / \ ------------- 根據(jù)入?yún)?nbsp;P1 進行策略分發(fā)
- * / \
- * +------+ +------+
- * | A | | B | ------- 第 2 層不同策略的實現(xiàn)
- * +------+ +------+
- * / \ / \ --------- 根據(jù)入?yún)?nbsp;P2 進行策略分發(fā)
- * / \ / \
- * +---+ +---+ +---+ +---+
- * |A1 | |A2 | |B1 | |B2 | ----- 第 3 層不同策略的實現(xiàn)
- * +---+ +---+ +---+ +---+
- *
繼承 AbstractStrategyRouter
如果子節(jié)點路由邏輯比較簡單,可以直接通過 if-else 進行分發(fā)。當然如果為了更好地性能、適應更復雜的分發(fā)邏輯也可以使用 Map 等保存映射。
對于實現(xiàn)了該抽象類的 Router 節(jié)點,只需要調用其 public R applyStrategy(T param) 方法即可獲取該節(jié)點的期望輸出。
框架會自動根據(jù)定義的路由邏輯將 param 傳遞到對應的子節(jié)點,再由子節(jié)點不斷向下分發(fā)直到葉子節(jié)點或可以給出業(yè)務輸出的一層。這個過程有點類似遞歸或者分治的思想。
②StrategyHandler 接口:
- /**
- * @author
- * @date
- */
- public interface StrategyHandler
{ - @SuppressWarnings("rawtypes")
- StrategyHandler DEFAULT = t -> null;
- /**
- * apply strategy
- *
- * @param param
- * @return
- */
- R apply(T param);
- }
除了根節(jié)點外,都要實現(xiàn) StrategyHandler
因此不再需要同時繼承 AbstractStrategyRouter
對于其他責任樹中的中間層節(jié)點,都需要同時繼承 Router 抽象類和實現(xiàn) Handler 接口。
在 R apply(T param); 方法中首先進行一定異常入?yún)r截,遵循 fail-fast 原則,避免將這一層可以攔截的錯誤傳遞到下一層,同時也要避免“越權”做非本層職責的攔截校驗,避免產生耦合,為后面業(yè)務拓展挖坑。
在攔截邏輯后直接調用本身 Router 的 public R applyStrategy(T param) 方法路由給下游節(jié)點即可。
完結撒花
至此,關于如何通過“責任樹模式”優(yōu)化這個需求場景的介紹就基本結束了,這不是一個復雜的需求,更不是一個多么精妙的優(yōu)化,這只是日常需求開發(fā)中通過設計模式優(yōu)化代碼的一個小例子。
最后再簡單聊聊我在日常需求開發(fā)過程中關于架構設計部分的一些思考。
其實并不是說用“if-else”很 Low,用設計模式就 Niubility,二者各有其擅長的應用場景,在合適的場景使用合適的代碼才是正道。
其實“if-else”足以滿足大部分日常需求的開發(fā),且簡單、靈活、可靠。
這里的“if-else”泛指樸素直白的編程模式,僅以實現(xiàn)需求業(yè)務功能為目的的編碼方式。
當然,有些同學不滿足于此,希望可以通過經過思考的、更優(yōu)的架構設計使代碼變的更簡潔、拓展性更好、性能更優(yōu)、可讀性更好等等。
不過對于此也存在反對的論述,謂之“過早優(yōu)化乃萬惡之源”。
這句話源自 Donald Knuth 他老人家:
we should forget about small efficiencies,say about 97% of the time:premature optimization is the root of all evil.
這句話我當然承認其正確性,但我同樣覺得需要注意以下幾點:
①任何“結論”都有其所處背景、上下文細節(jié)等,通過一句話指導工作是不成立的。
優(yōu)秀的架構師可以給出架構設計是在理論基礎、大量實踐、不斷思考總結以及無數(shù)采坑的經驗的基礎上得來的,而不是他知道一句別人都不知道的“咒語”。
②Knuth 這句話更偏重于反對奇技淫巧、細枝末節(jié)的性能優(yōu)化,因為在“過早”的時候無法準確獲知系統(tǒng)的瓶頸且局部的優(yōu)化不僅不能帶來收益,反而會造成更大的代價。
他批評的恰恰是不著眼于整體架構的局部視角對系統(tǒng)的破壞,而架構設計正是需要從整體視角去做選擇與權衡。因此將 Knuth 這句話直接推廣到“架構設計”上并不妥當。
③很多人覺得在項目開發(fā)時需求經?!八蚕⑷f變”、“朝令夕改”,而做優(yōu)化又需要花費大量時間思考,根本沒有精力優(yōu)化。
我認為這種論述也是不成立的,憑什么認為等到壞味道嚴重、歷史包袱沉重的時候就有精力、能力和膽量做優(yōu)化了呢?
④何時是所謂的“不早”很難界定,其實我們永遠都無法確定自己掌握了足夠的細節(jié)可以進行絕對正確的優(yōu)化。
在現(xiàn)實世界中,受到時間維度的限制,我們永遠無法達成全局最優(yōu),只能以局部最優(yōu)不斷去逼近全局最優(yōu)。我覺得等到壞味道嚴重不得不重構的時候才想起優(yōu)化已為時過晚。
⑤這句話不應該成為不做設計的借口,即使最終提交的代碼仍是“if-else”版本,也不應省略思考、推演、權衡的過程,日常需求是練兵場,是精進技術的必經之路。
所以,我覺得不要被這句話束縛手腳,當然更不要閉門造車,在開發(fā)過程中勤于思考,向更有經驗的人請教,在架構設計上不斷學習、探索,才能擺脫日復一日通過“if-else”堆砌業(yè)務邏輯的循環(huán)。
作者:尋弈
編輯:陶家龍
出處:轉載自公眾號閑魚技術(ID:XYtech_Alibaba)
文章題目:扛不住了,老大讓我干掉if-else
分享網(wǎng)址:http://fisionsoft.com.cn/article/cdoccip.html


咨詢
建站咨詢
