日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

對于業務開發來說,業務邏輯的復雜是必然的。隨著業務發展,需求只會越來越復雜,為了考慮到各種各樣的情況,代碼中不可避免的會出現很多 if-else。

剛來的大神徹底干掉了代碼中的if else...

 


圖片來自 Pexels

一旦代碼中 if-else 過多,就會大大的影響其可讀性和可維護性。

剛來的大神徹底干掉了代碼中的if else...

 

首先可讀性,不言而喻,過多的 if-else 代碼和嵌套,會使閱讀代碼的人很難理解到底是什么意思。尤其是那些沒有注釋的代碼。

其次是可維護性,因為 if-else 特別多,想要新加一個分支的時候,就會很難添加,極其容易影響到其他的分支。

筆者曾經看到過一個支付的核心應用,這個應用支持了很多業務的線上支付功能,但是每個業務都有很多定制的需求,所以很多核心的代碼中都有一大坨 if-else。

每個新業務需要定制的時候,都把自己的 if 放到整個方法的最前面,以保證自己的邏輯可以正常執行。這種做法,后果可想而知。

其實,if-else 是有辦法可以消除掉的,其中比較典型的并且使用廣泛的就是借助策略模式和工廠模式,準確的說是利用這兩個設計模式的思想,徹底消滅代碼中的 if-else。

本文就結合這兩種設計模式,介紹如何消除 if-else,并且,還會介紹如何和 Spring 框架結合,這樣讀者看完本文之后就可以立即應用到自己的項目中。

本文涉及到一些代碼,但是作者盡量用通俗的例子和偽代碼等形式使內容不那么枯燥。

惡心的 if-else

假設我們要做一個外賣平臺,有這樣的需求:

  1. 外賣平臺上的某家店鋪為了促銷,設置了多種會員優惠,其中包含超級會員折扣 8 折、普通會員折扣 9 折和普通用戶沒有折扣三種。
  2. 希望用戶在付款的時候,根據用戶的會員等級,就可以知道用戶符合哪種折扣策略,進而進行打折,計算出應付金額。
  3. 隨著業務發展,新的需求要求專屬會員要在店鋪下單金額大于 30 元的時候才可以享受優惠。
  4. 接著,又有一個變態的需求,如果用戶的超級會員已經到期了,并且到期時間在一周內,那么就對用戶的單筆訂單按照超級會員進行折扣,并在收銀臺進行強提醒,引導用戶再次開通會員,而且折扣只進行一次。

那么,我們可以看到以下偽代碼:

public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) { if (用戶是專屬會員) { if (訂單金額大于30元) { returen 7折價格; } } if (用戶是超級會員) { return 8折價格; } if (用戶是普通會員) { if(該用戶超級會員剛過期并且尚未使用過臨時折扣){ 臨時折扣使用次數更新(); returen 8折價格; } return 9折價格; } return 原價; } 

以上,就是對于這個需求的一段價格計算邏輯,使用偽代碼都這么復雜,如果是真的寫代碼,那復雜度可想而知。

這樣的代碼中,有很多 if-else,并且還有很多的 if-else 的嵌套,無論是可讀性還是可維護性都非常低。那么,如何改善呢?

策略模式

接下來,我們嘗試引入策略模式來提升代碼的可維護性和可讀性。

首先,定義一個接口:

/** * @author mhcoding */ public interface UserPayService { /** * 計算應付價格 */ public BigDecimal quote(BigDecimal orderPrice); } 

接著定義幾個策略類:

/** * @author mhcoding */ public class ParticularlyVipPayService implements UserPayService { @Override public BigDecimal quote(BigDecimal orderPrice) { if (消費金額大于30元) { return 7折價格; } } } public class SuperVipPayService implements UserPayService { @Override public BigDecimal quote(BigDecimal orderPrice) { return 8折價格; } } public class VipPayService implements UserPayService { @Override public BigDecimal quote(BigDecimal orderPrice) { if(該用戶超級會員剛過期并且尚未使用過臨時折扣){ 臨時折扣使用次數更新(); returen 8折價格; } return 9折價格; } } 

引入了策略之后,我們可以按照如下方式進行價格計算:

/** * @author mhcoding */ public class Test { public static void main(String[] args) { UserPayService strategy = new VipPayService(); BigDecimal quote = strategy.quote(300); System.out.println("普通會員商品的最終價格為:" + quote.doubleValue()); strategy = new SuperVipPayService(); quote = strategy.quote(300); System.out.println("超級會員商品的最終價格為:" + quote.doubleValue()); } } 

以上,就是一個例子,可以在代碼中 New 出不同的會員的策略類,然后執行對應的計算價格的方法。

但是,真正在代碼中使用,比如在一個 Web 項目中使用,上面這個 Demo 根本沒辦法直接用。

首先,在 Web 項目中,上面我們創建出來的這些策略類都是被 Spring 托管的,我們不會自己去 New 一個實例出來。

其次,在 Web 項目中,如果真要計算價格,也是要事先知道用戶的會員等級,比如從數據庫中查出會員等級,然后根據等級獲取不同的策略類執行計算價格方法。

那么,Web 項目中真正的計算價格的話,偽代碼應該是這樣的:

/** * @author mhcoding */ public BigDecimal calPrice(BigDecimal orderPrice,User user) { String vipType = user.getVipType(); if (vipType == 專屬會員) { //偽代碼:從Spring中獲取超級會員的策略對象 UserPayService strategy = Spring.getBean(ParticularlyVipPayService.class); return strategy.quote(orderPrice); } if (vipType == 超級會員) { UserPayService strategy = Spring.getBean(SuperVipPayService.class); return strategy.quote(orderPrice); } if (vipType == 普通會員) { UserPayService strategy = Spring.getBean(VipPayService.class); return strategy.quote(orderPrice); } return 原價; } 

通過以上代碼,我們發現,代碼可維護性和可讀性好像是好了一些,但是好像并沒有減少 if-else 啊。

但是,策略模式的使用上,還是有一個比較大的缺點的:客戶端必須知道所有的策略類,并自行決定使用哪一個策略類。這就意味著客戶端必須理解這些算法的區別,以便適時選擇恰當的算法類。

也就是說,雖然在計算價格的時候沒有 if-else 了,但是選擇具體的策略的時候還是不可避免的還是要有一些 if-else。

另外,上面的偽代碼中,從 Spring 中獲取會員的策略對象我們是偽代碼實現的,那么代碼到底該如何獲取對應的 Bean 呢?

接下來我們看如何借助 Spring 和工廠模式,解決上面這些問題。

工廠模式

為了方便我們從 Spring 中獲取 UserPayService 的各個策略類,我們創建一個工廠類:

/** * @author mhcoding */ public class UserPayServiceStrategyFactory { private static Map<String,UserPayService> services = new ConcurrentHashMap<String,UserPayService>(); public static UserPayService getByUserType(String type){ return services.get(type); } public static void register(String userType,UserPayService userPayService){ Assert.notNull(userType,"userType can't be null"); services.put(userType,userPayService); } } 

這個 UserPayServiceStrategyFactory 中定義了一個 Map,用來保存所有的策略類的實例,并提供一個 getByUserType 方法,可以根據類型直接獲取對應的類的實例。還有一個 Register 方法,這個后面再講。

有了這個工廠類之后,計算價格的代碼即可得到大大的優化:

/** * @author mhcoding */ public BigDecimal calPrice(BigDecimal orderPrice,User user) { String vipType = user.getVipType(); UserPayService strategy = UserPayServiceStrategyFactory.getByUserType(vipType); return strategy.quote(orderPrice); } 

以上代碼中,不再需要 if-else 了,拿到用戶的 vip 類型之后,直接通過工廠的 getByUserType 方法直接調用就可以了。

通過策略+工廠,我們的代碼很大程度的優化了,大大提升了可讀性和可維護性。

但是,上面還遺留了一個問題,那就是 UserPayServiceStrategyFactory 中用來保存所有的策略類的實例的 Map 是如何被初始化的?各個策略的實例對象如何塞進去的呢?

Spring Bean 的注冊

還記得我們前面定義的 UserPayServiceStrategyFactory 中提供了的 Register 方法嗎?他就是用來注冊策略服務的。

接下來,我們就想辦法調用 Register 方法,把 Spring 通過 IOC 創建出來的 Bean 注冊進去就行了。

這種需求,可以借用 Spring 中提供的 InitializingBean 接口,這個接口為 Bean 提供了屬性初始化后的處理方法。

它只包括 afterPropertiesSet 方法,凡是繼承該接口的類,在 Bean 的屬性初始化后都會執行該方法。

那么,我們將前面的各個策略類稍作改造即可:

/** * @author mhcoding */ @Service public class ParticularlyVipPayService implements UserPayService,InitializingBean { @Override public BigDecimal quote(BigDecimal orderPrice) { if (消費金額大于30元) { return 7折價格; } } @Override public void afterPropertiesSet() throws Exception { UserPayServiceStrategyFactory.register("ParticularlyVip",this); } } @Service public class SuperVipPayService implements UserPayService ,InitializingBean{ @Override public BigDecimal quote(BigDecimal orderPrice) { return 8折價格; } @Override public void afterPropertiesSet() throws Exception { UserPayServiceStrategyFactory.register("SuperVip",this); } } @Service public class VipPayService implements UserPayService,InitializingBean { @Override public BigDecimal quote(BigDecimal orderPrice) { if(該用戶超級會員剛過期并且尚未使用過臨時折扣){ 臨時折扣使用次數更新(); returen 8折價格; } return 9折價格; } @Override public void afterPropertiesSet() throws Exception { UserPayServiceStrategyFactory.register("Vip",this); } } 

只需要每一個策略服務的實現類都實現 InitializingBean 接口,并實現其 afterPropertiesSet 方法,在這個方法中調用 UserPayServiceStrategyFactory.register 即可。

這樣,在 Spring 初始化的時候,當創建 VipPayService、SuperVipPayService 和 ParticularlyVipPayService 的時候,會在 Bean 的屬性初始化之后,把這個 Bean 注冊到 UserPayServiceStrategyFactory 中。

以上代碼,其實還是有一些重復代碼的,這里面還可以引入模板方法模式進一步精簡,這里就不展開了。

還有就是,UserPayServiceStrategyFactory.register 調用的時候,第一個參數需要傳一個字符串,這里的話其實也可以優化掉。

比如使用枚舉,或者在每個策略類中自定義一個 getUserType 方法,各自實現即可。

總結

本文,我們通過策略模式、工廠模式以及 Spring 的 InitializingBean,提升了代碼的可讀性以及可維護性,徹底消滅了一坨 if-else。

文中的這種做法,大家可以立刻嘗試起來,這種實踐,是我們日常開發中經常用到的,而且還有很多衍生的用法,也都非常好用。有機會后面再介紹。

其實,如果讀者們對策略模式和工廠模式了解的話,文中使用的并不是嚴格意義上面的策略模式和工廠模式。

首先,策略模式中重要的 Context 角色在這里面是沒有的,沒有 Context,也就沒有用到組合的方式,而是使用工廠代替了。

另外,這里面的 UserPayServiceStrategyFactory 其實只是維護了一個 Map,并提供了 Register 和 Get 方法而已,而工廠模式其實是幫忙創建對象的,這里并沒有用到。

所以,讀者不必糾結于到底是不是真的用了策略模式和工廠模式。而且,這里面也再擴展一句,所謂的 GOF 23 種設計模式,無論從哪本書或者哪個博客看,都是簡單的代碼示例,但是我們日常開發很多都是基于 Spring 等框架的,根本沒辦法直接用的。

所以,對于設計模式的學習,重要的是學習其思想,而不是代碼實現!!!希望通過這樣的文章,讀者可以真正的在代碼中使用上設計模式。

分享到:
標簽:代碼
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定