如何在程式碼中應用設計模式

寧願。發表於2019-03-11

為什麼要使用設計模式

因為我們的專案的需求是永遠在變的,為了應對這種變化,使得我們的程式碼能夠輕易的實現解耦和擴充。如果能夠保證程式碼一次寫好以後都不會再改變了,那可以想怎麼寫怎麼寫了。

如何判斷那裡需要使用設計模式

image

在我們實現中,有一些程式碼是一次寫好後續基本不會改變的,或者不太需要擴充套件的,比如一些工具類等。有一部分是會經常變得,設計模式大多都應用在需求會變化的這一部分。分析這些程式碼會如何變,選擇合適的設計模式來優化這部分程式碼。

以促銷活動需求為例

需求

為了促進商品的銷售,各大電商品臺會在平時或者一些節日的時候退出一些促銷活動刺激使用者消費,活動的型別可能會各不相同,如下:

  • 滿減,滿400減20
  • 代金卷,瑪莎拉蒂5元代金卷
  • 折扣,9折,8折
  • 每滿減,每滿200減10
  • 等等

其中有些可以疊加,有些只能單獨使用。

簡單實現

上面的需求看起來還是比較簡單的,但是如果考慮到我們是不可能一次定義好所有的促銷活動型別,後續我們可能會隨時都新增新的型別,要保證能夠簡單的實現功能擴充套件,那就比較麻煩了。

先拿到需求的時候,也不用去想那麼多,挽起袖子就是一通操作:

public class OrderPromotion {

    public BigDecimal promotion(Order order, int[] promotions){
        for(int promotion:promotions){
            switch (promotion){
                case 1:
                    //計算該型別折扣後的價格
                    break;
                case 2:
                    //計算該型別折扣後的價格
                    break;
                case 3:
                    //計算該型別折扣後的價格
                    break;
                //....
            }
        }
        return order.getResultPrice();
    }
}
複製程式碼

單從功能實現上來說,上面的程式碼已經完成了基本功能了。但是上面的程式碼也是致命的,雖然看起來很簡單,但是那隻不過是因為大多數功能都用註釋代替了,換成實際程式碼的話一個方法可能就得上千行。

尤其是當我們需要新增新的促銷活動的話就需要在switch中新增新的型別,這對於開發來說簡直是災難,並且維護這些程式碼也是一個麻煩。

優化一:單一職責原則

上面的程式碼中,promotion(...)方法直接完成了所有的工作,但是咋我們實際實現中最好讓一個方法的職責單一,只完成某一個功能,所以這裡我們將對摺扣型別的判斷和計算價格分開:

public class OrderPromotion {

    public BigDecimal promotion(Order order, int[] promotions){
        for(int promotion:promotions){
            switch (promotion){
                case 1:
                    calculate1(order);
                    break;
                case 2:
                    calculate2(order);
                    break;
                case 3:
                    calculate3(order);
                    break;
                //more promotion
            }
        }
        return order.getResultPrice();
    }
    
    public void calculate1(Order order){
        //計算使用折扣一後的價格
    }

    public void calculate2(Order order){
        //計算使用折扣二後的價格
    }

    public void calculate3(Order order){
        //計算使用折扣三後的價格
    }
    
    //more calculate
    
}
複製程式碼

這裡我們將折扣型別的判斷和計算價格分開,使得promotion(...)方法的程式碼量大大降低,提升了程式碼的可讀性。

優化二:策略模式

上面優化後的程式碼提升了原有程式碼的可讀性,但是原來OrderPromotion類程式碼大爆炸的問題還是沒有解決。針對這個問題,我們希望能夠將計算的程式碼和當前程式碼分離開,首先我們能想到的就是定義一個類,然後將計算的程式碼複製到這個類中,需要的時候就呼叫。這樣到的確是分離開了,但是完全是治標不治本。在新增新的促銷活動是兩個類都要改。

所以我們希望能夠將不同的促銷活動的實現分離開,這樣對每一種活動的實現都是分開的,修改也不會影響其他的,基於此我們完全可以選擇策略模式來實現。

策略模式

策略模式的思想是針對一組演算法,將每一種演算法都封裝到具有共同介面的獨立的類中,從而是它們可以相互替換。策略模式的最大特點是使得演算法可以在不影響客戶端的情況下發生變化,從而改變不同的功能。

promotion

public class OrderPromotion {

    public BigDecimal promotion(Order order, int[] promotions){
        for(int promotion:promotions){
            switch (promotion){
                case 1:
                    new PromotionType1Calculate(order);
                    break;
                case 2:
                    new PromotionType1Calculate(order);
                    break;
                case 3:
                    new PromotionType1Calculate(order);
                    break;
                //more promotion
            }
        }
        return order.getResultPrice();
    }
}
複製程式碼

上面的程式碼很明顯已經精簡很多了,到了現在如果需要新增一個促銷活動的話只需定義一個促銷類,實現PromotionCalculation介面然後在switch中新增即可。

優化三:工廠模式

上面的程式碼雖然已經將促銷活動的實現分離開了,但是OrderPromotion還是一直在變得,每一次新增或者下線活動都需要修改該類。現在我們希望OrderPromotion是不變的,將PromotionCalculation的例項化剝離開來。建立類很明顯是使用工廠設計模式了。

OrderPromotion

public class OrderPromotion {

    public BigDecimal promotion(Order order, int[] promotions){
        for(int promotion:promotions){
            PromotionFactory.getPromotionCalculate(promotion).calculate(order);
        }
        return order.getResultPrice();
    }
}
複製程式碼

類的建立工作交給工廠來實現。

PromotionFactory

public class PromotionFactory {
    
    public static PromotionCalculate getPromotionCalculate(int promotion){
        switch (promotion){
            case 1:
                return new PromotionType1Calculate(order);
            break;
            case 2:
                return new PromotionType1Calculate(order);
            break;
            case 3:
                return new PromotionType1Calculate(order);
            break;
            //more promotion
        }
        return null;
    }
}
複製程式碼

使用工廠模式後OrderPromotion類就不需要改了,每一次新增新的促銷活動後只需要在工廠類中新增即可。

優化四:配置+反射

上面的程式碼還存在的問題在於每一次需要新增新的促銷活動的時候還是需要修改工廠類中的程式碼,這裡我們通過配置檔案加反射的方式來解決。

定義對映配置檔案

mapping.properties

1=design.order.PromotionType1Calculate
2=design.order.PromotionType2Calculate
3=design.order.PromotionType3Calculate
複製程式碼

PromotionFactory

public class PromotionFactory {
    
    private static Map<Integer, String> mapping = new HashMap<Integer, String>();

    static {
        try {
            Properties pps = new Properties();
            pps.load(new FileInputStream("Test.properties"));
            Iterator<String> iterator = pps.stringPropertyNames().iterator();
            while(iterator.hasNext()){
                String key=iterator.next();
                mapping.put(Integer.valueOf(key), pps.getProperty(key));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static PromotionCalculate getPromotionCalculate(int promotion) throws Exception {
        if(mapping.containsKey(promotion)){
            String beanName = mapping.get(promotion);
            return Class.forName(beanName).newInstance();
        }
        return null;
    }
}
複製程式碼

通過上面的程式碼就可以實現不改變已有程式碼的前提下實現對功能的靈活擴充套件。當然,這裡的程式碼只是作為演示用的,實際上可以改進的地方還有不少,像最後反射效率較低,也可以通過其他的方式來實現。

小結

設計模式是我們一定要了解的東西,熟悉設計模式能讓我們設計出易於擴充套件和維護的程式碼結構。但是並不是任何地方都需要上設計模式,應該結合我們的專案實際進行分析是否需要設計模式,使用哪種設計模式。

相關文章