業務複雜=if else?剛來的大神居然用策略+工廠完全乾掉了他們!

​對於業務開發來講,業務邏輯的複雜是必然的,隨着業務發展,需求只會愈來愈複雜,爲了考慮到各類各樣的狀況,代碼中不可避免的會出現不少if-else。

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

首先可讀性,不言而喻,過多的if-else代碼和嵌套,會使閱讀代碼的人很難理解究竟是什麼意思。尤爲是那些沒有註釋的代碼。算法

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

筆者曾經看到過一個支付的核心應用,這個應用支持了不少業務的線上支付功能,可是每一個業務都有不少定製的需求,因此不少核心的代碼中都有一大坨if-else。設計模式

每一個新業務須要定製的時候,都把本身的if放到整個方法的最前面,以保證本身的邏輯能夠正常執行。這種作法,後果可想而知。bash

其實,if-else是有辦法能夠消除掉的,其中比較典型的而且使用普遍的就是藉助策略模式和工廠模式,準確的說是利用這兩個設計模式的思想,完全消滅代碼中的if-else。markdown

本文,就結合這兩種設計模式,介紹如何消除if-else,而且,還會介紹如何和Spring框架結合,這樣讀者看完本文以後就能夠當即應用到本身的項目中。框架

本文涉及到一些代碼,可是做者儘可能用通俗的例子和僞代碼等形式使內容不那麼枯燥。ide

噁心的if-else

假設咱們要作一個外賣平臺,有這樣的需求:oop

一、外賣平臺上的某家店鋪爲了促銷,設置了多種會員優惠,其中包含超級會員折扣8折、普通會員折扣9折和普通用戶沒有折扣三種。學習

二、但願用戶在付款的時候,根據用戶的會員等級,就能夠知道用戶符合哪一種折扣策略,進而進行打折,計算出應付金額。

三、隨着業務發展,新的需求要求專屬會員要在店鋪下單金額大於30元的時候才能夠享受優惠。

四、接着,又有一個變態的需求,若是用戶的超級會員已經到期了,而且到期時間在一週內,那麼就對用戶的單筆訂單按照超級會員進行折扣,並在收銀臺進行強提醒,引導用戶再次開通會員,並且折扣只進行一次。

那麼,咱們能夠看到如下僞代碼:

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等框架的,根本沒辦法直接用的。

因此,對於設計模式的學習,重要的是學習其思想,而不是代碼實現!!!

若是讀者們感興趣,後續能夠出更多的設計模式和Spring等框架結合使用的最佳實踐。但願經過這樣的文章,讀者能夠真正的在代碼中使用上設計模式。

相關文章
相關標籤/搜索