您好,登錄后才能下訂單哦!
這篇文章主要講解了“如何根據入參 p1、p2、p3 等的不同組合進行策略定位”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“如何根據入參 p1、p2、p3 等的不同組合進行策略定位”吧!
問題背景
最近開發了一個需求,該接口需要根據 p1、p2、p3、version 多個入參的不同組合按照其對應的業務策略給出結果數據。
由于該接口已經開發了三期了,每次開發新一期的需求時為了兼容老的業務邏輯,大家都傾向于不刪不改只新增。
因此這塊代碼已經產生了一些“壞味道”,函數入口通過不斷添加“衛語句”判斷 version 的方式跳轉到新一期的業務邏輯方法中。
而每一期的業務邏輯也是通過 p1、p2、p3 的 if-else 組合形成不同的分支邏輯。
這已經是我簡化后的表述,總之剛開始對于我這個新同學來說,梳理這塊業務代碼著實花了一些功夫。
而且,這塊邏輯相當于是一個業務上的通用能力,未來一定還會有五期、六期、N 期的需求進來,入參的取值也會不斷拓展,因此以現有方式膨脹下去只會“壞味道”會越來越重。
總結一下,當前場景面臨的問題是:
如何解決接口升級,在保證兼容老版本的情況下輕松開發新版本業務邏輯?
如何根據入參 p1、p2、p3 等的不同組合進行策略定位?
解決思路
在思考解決方案時,很容易想到兩種可以優化類似場景的設計模式:責任鏈模式和策略模式。
責任鏈模式
責任鏈模式是實現了類似“流水線”結構的逐級處理,通常是一條鏈式結構,將“抽象處理者”的不同實現串聯起來。
如果當前節點能夠處理任務則直接處理掉,如果無法處理則委托給責任鏈的下一個節點,如此往復直到有節點可以處理這個任務。
我們可以通過責任鏈模式完成對不同 version 業務邏輯隔離的處理,比如節點 1 處理 version=1 的請求,節點 2 處理 version=2 的請求等等。
但問題在于我們遇到的場景還需要根據一定策略,路由到不同的下游節點進行處理。這就是策略模式擅長解決的問題了。
策略模式
策略模式的目的是將算法的使用與定義解耦,能夠實現根據規則路由到不同策略類進行處理。
我們可以通過策略模式解決根據不同參數組合執行不同業務邏輯的場景。但是我們的場景僅僅通過一層策略路由無法滿足任務處理需求。請求的分層處理又是責任鏈模式所擅長的了。
可以看到,兩種設計模式都不完全符合目前這個場景:責任鏈模式可以實現逐級委托,但每一級又不能像策略模式那樣路由到不同的處理者上;策略模式通常只有一層路由,不易實現多個參數的策略組合。
因此我們自然而然地可以想到:是不是可以將兩種模式結合起來?
廣義責任鏈模式:責任樹模式
將責任鏈與策略模式融合,即成為了一種廣義的責任鏈模式,我簡稱為“責任樹模式”。
這種模式不僅可以完成任務的逐級委托,也可以在任一級選擇不同的下游策略進行處理。
那么問題來了,如何通過責任樹模式解決前面我們遇到的問題呢?
首先看如何解決第一個問題,新老接口的隔離和兼容:可以將接口每個版本的邏輯作為一個責任樹上第一層的不同實現,如分別對應上圖中的 Strategy1、Strategy2、Strategy3 節點。
這樣在接口入口,就首先把策略路由到不同的分支上去。如果沒有節點命中,則不再向下游委托直接返回錯誤。
然后第二個問題,參數的組合定位到不同的策略實現上:同樣的思路,一個參數對應責任樹上的一層的路由,將該參數的不同取值路由到下一層的不同實現即可,這樣逐級委托,后面新增入參的枚舉值、甚至再拓展新的入參都可以非常方便地進行拓展。
優化收益
將這塊業務通過“責任樹模式”重構之后,可以收獲以下幾個收益點:
后續迭代人力成本降低。
代碼結構更清晰,可維護性提升:沒有了各種“衛語句”的跳轉 & 維護性巨差的巨型方法,函數可以收斂在理想的 50 行內。
后續新增需求修改代碼不易出錯:策略間隔離,不需要完整看一遍大函數理清邏輯再修改,只需要無腦添加一條路由 + 新的策略實現方法即可。
問題易定位:同樣由于策略間隔離,調試時可以直接定位到指定策略的業務邏輯代碼,不需要逐句排查。
相信有開發經驗的同學應該都有體會,即使是自己寫過的代碼,一陣子不看也會忘掉,等到再有修改時,還要順著代碼理一遍邏輯,如果文檔、注釋沒寫好,那就更加酸爽了。因此,將巨型函數拆分解耦非常重要。
抽象框架
雖然通過“責任樹模式”解決了我這個需求開發中遇到的問題,但是類似的問題還是普遍存在的。
本著助(shǎo)人(zào)為(lún)樂(zi)的精神,我更進一步,將責任樹模式抽象出一個通用的框架,方便大家在遇到類似問題時快速“種樹”。
這個框架由一個 Router 和 Handler 組成:
Router 是一個抽象類,負責定義如何路由到下游的多個子節點。
Handler 是接口,負責實現每個節點的業務邏輯。
我們可以非常方便地通過 Router 和 Handler 的組合拼裝成整棵樹的結構。
從圖中我們可以看出以下幾個要點:
除了根節點(入口)外,每個節點都實現了 Handler 接口。根節點只繼承 Router 抽象類。
所有葉子節點只實現 Handler 接口而無需繼承 Router 抽象類(無需再向下委托)。
除了根節點和葉子節點外的其他節點,都是上一層的 Handler,同時是下一層的 Router。
那么我們話不多說,先看下框架代碼。
①AbstractStrategyRouter 抽象類:
/** * 通用的“策略樹“框架,通過樹形結構實現分發與委托,每層通過指定的參數進行向下分發委托,直到達到最終的執行者。 * 該框架包含兩個類:{@code StrategyHandler} 和 {@code AbstractStrategyRouter} * 其中:通過實現 {@code AbstractStrategyRouter} 抽象類完成對策略的分發, * 實現 {@code StrategyHandler} 接口來對策略進行實現。 * 像是第二層 A、B 這樣的節點,既是 Root 節點的策略實現者也是策略A1、A2、B1、B2 的分發者,這樣的節點只需要 * 同時繼承 {@code StrategyHandler} 和實現 {@code AbstractStrategyRouter} 接口就可以了。 * * <pre> * +---------+ * | Root | ----------- 第 1 層策略入口 * +---------+ * / \ ------------- 根據入參 P1 進行策略分發 * / \ * +------+ +------+ * | A | | B | ------- 第 2 層不同策略的實現 * +------+ +------+ * / \ / \ --------- 根據入參 P2 進行策略分發 * / \ / \ * +---+ +---+ +---+ +---+ * |A1 | |A2 | |B1 | |B2 | ----- 第 3 層不同策略的實現 * +---+ +---+ +---+ +---+ * </pre> * * @author * @date * @see StrategyHandler */ @Component public abstract class AbstractStrategyRouter<T, R> { /** * 策略映射器,根據指定的入參路由到對應的策略處理者。 * * @param <T> 策略的入參類型 * @param <R> 策略的返回值類型 */ public interface StrategyMapper<T, R> { /** * 根據入參獲取到對應的策略處理者。可通過 if-else 實現,也可通過 Map 實現。 * * @param param 入參 * @return 策略處理者 */ StrategyHandler<T, R> get(T param); } private StrategyMapper<T, R> strategyMapper; /** * 類初始化時注冊分發策略 Mapper */ @PostConstruct private void abstractInit() { strategyMapper = registerStrategyMapper(); Objects.requireNonNull(strategyMapper, "strategyMapper cannot be null"); } @Getter @Setter @SuppressWarnings("unchecked") private StrategyHandler<T, R> defaultStrategyHandler = StrategyHandler.DEFAULT; /** * 執行策略,框架會自動根據策略分發至下游的 Handler 進行處理 * * @param param 入參 * @return 下游執行者給出的返回值 */ public R applyStrategy(T param) { final StrategyHandler<T, R> strategyHandler = strategyMapper.get(param); if (strategyHandler != null) { return strategyHandler.apply(param); } return defaultStrategyHandler.apply(param); } /** * 抽象方法,需要子類實現策略的分發邏輯 * * @return 分發邏輯 Mapper 對象 */ protected abstract StrategyMapper<T, R> registerStrategyMapper(); }
繼承 AbstractStrategyRouter
如果子節點路由邏輯比較簡單,可以直接通過 if-else 進行分發。當然如果為了更好地性能、適應更復雜的分發邏輯也可以使用 Map 等保存映射。
對于實現了該抽象類的 Router 節點,只需要調用其 public R applyStrategy(T param) 方法即可獲取該節點的期望輸出。
框架會自動根據定義的路由邏輯將 param 傳遞到對應的子節點,再由子節點不斷向下分發直到葉子節點或可以給出業務輸出的一層。這個過程有點類似遞歸或者分治的思想。
②StrategyHandler 接口:
/** * @author * @date */ public interface StrategyHandler<T, R> { @SuppressWarnings("rawtypes") StrategyHandler DEFAULT = t -> null; /** * apply strategy * * @param param * @return */ R apply(T param); }
除了根節點外,都要實現 StrategyHandler
因此不再需要同時繼承 AbstractStrategyRouter
對于其他責任樹中的中間層節點,都需要同時繼承 Router 抽象類和實現 Handler 接口。
在 R apply(T param); 方法中首先進行一定異常入參攔截,遵循 fail-fast 原則,避免將這一層可以攔截的錯誤傳遞到下一層,同時也要避免“越權”做非本層職責的攔截校驗,避免產生耦合,為后面業務拓展挖坑。
在攔截邏輯后直接調用本身 Router 的 public R applyStrategy(T param) 方法路由給下游節點即可。
感謝各位的閱讀,以上就是“如何根據入參 p1、p2、p3 等的不同組合進行策略定位”的內容了,經過本文的學習后,相信大家對如何根據入參 p1、p2、p3 等的不同組合進行策略定位這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。