天天看點

如何在代碼中應用設計模式?

為什麼要使用設計模式

因為我們的項目的需求是永遠在變的,為了應對這種變化,使得我們的代碼能夠輕易的實作解耦和拓展。如果能夠保證代碼一次寫好以後都不會再改變了,那可以想怎麼寫怎麼寫了。

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

以促銷活動需求為例

需求

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

滿減,滿400減20

代金卷,瑪莎拉蒂5元代金卷

折扣,9折,8折

每滿減,每滿200減10

等等

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

簡單實作

上面的需求看起來還是比較簡單的,但是如果考慮到我們是不可能一次定義好所有的促銷活動類型,後續我們可能會随時都添加新的類型,要保證能夠簡單的實作功能擴充,那就比較麻煩了。Spring 架構用到的 9 個設計模式彙總,這個你知道嗎?

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

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(…)方法的代碼量大大降低,提升了代碼的可讀性。面象對象設計6大原則之一:單一職責原則,這篇也推薦大家看下。

優化二:政策模式

上面優化後的代碼提升了原有代碼的可讀性,但是原來OrderPromotion類代碼大爆炸的問題還是沒有解決。

針對這個問題,我們希望能夠将計算的代碼和目前代碼分離開,首先我們能想到的就是定義一個類,然後将計算的代碼複制到這個類中,需要的時候就調用。這樣到的确是分離開了,但是完全是治标不治本。在添加新的促銷活動是兩個類都要改。

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

政策模式

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

如何在代碼中應用設計模式?
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

如何在代碼中應用設計模式?
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;  
    }  
}      

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

小結

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

推薦去我的部落格閱讀更多:

1.Java JVM、集合、多線程、新特性系列教程

2.Spring MVC、Spring Boot、Spring Cloud 系列教程

3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

4.Java、後端、架構、阿裡巴巴等大廠最新面試題

生活很美好,明天見~