如何在代碼中應(yīng)用設(shè)計(jì)模式?
為什么要使用設(shè)計(jì)模式
因?yàn)槲覀兊捻?xiàng)目的需求是永遠(yuǎn)在變的,為了應(yīng)對(duì)這種變化,使得我們的代碼能夠輕易的實(shí)現(xiàn)解耦和拓展。如果能夠保證代碼一次寫(xiě)好以后都不會(huì)再改變了,那可以想怎么寫(xiě)怎么寫(xiě)了。
如何判斷那里需要使用設(shè)計(jì)模式
在我們實(shí)現(xiàn)中,有一些代碼是一次寫(xiě)好后續(xù)基本不會(huì)改變的,或者不太需要擴(kuò)展的,比如一些工具類(lèi)等。有一部分是會(huì)經(jīng)常變得,設(shè)計(jì)模式大多都應(yīng)用在需求會(huì)變化的這一部分。分析這些代碼會(huì)如何變,選擇合適的設(shè)計(jì)模式來(lái)優(yōu)化這部分代碼。
以促銷(xiāo)活動(dòng)需求為例
需求
為了促進(jìn)商品的銷(xiāo)售,各大電商品臺(tái)會(huì)在平時(shí)或者一些節(jié)日的時(shí)候退出一些促銷(xiāo)活動(dòng)刺激用戶(hù)消費(fèi),活動(dòng)的類(lèi)型可能會(huì)各不相同,如下:
- 滿減,滿400減20
- 代金卷,瑪莎拉蒂5元代金卷
- 折扣,9折,8折
- 每滿減,每滿200減10
- 等等
其中有些可以疊加,有些只能單獨(dú)使用。
簡(jiǎn)單實(shí)現(xiàn)
上面的需求看起來(lái)還是比較簡(jiǎn)單的,但是如果考慮到我們是不可能一次定義好所有的促銷(xiāo)活動(dòng)類(lèi)型,后續(xù)我們可能會(huì)隨時(shí)都添加新的類(lèi)型,要保證能夠簡(jiǎn)單的實(shí)現(xiàn)功能擴(kuò)展,那就比較麻煩了。Spring 框架用到的 9 個(gè)設(shè)計(jì)模式匯總,這個(gè)你知道嗎?
先拿到需求的時(shí)候,也不用去想那么多,挽起袖子就是一通操作:
- public class OrderPromotion {
- public BigDecimal promotion(Order order, int[] promotions){
- for(int promotion:promotions){
- switch (promotion){
- case 1:
- //計(jì)算該類(lèi)型折扣后的價(jià)格
- break;
- case 2:
- //計(jì)算該類(lèi)型折扣后的價(jià)格
- break;
- case 3:
- //計(jì)算該類(lèi)型折扣后的價(jià)格
- break;
- //....
- }
- }
- return order.getResultPrice();
- }
- }
單從功能實(shí)現(xiàn)上來(lái)說(shuō),上面的代碼已經(jīng)完成了基本功能了。
但是上面的代碼也是致命的,雖然看起來(lái)很簡(jiǎn)單,但是那只不過(guò)是因?yàn)榇蠖鄶?shù)功能都用注釋代替了,換成實(shí)際代碼的話一個(gè)方法可能就得上千行。
尤其是當(dāng)我們需要添加新的促銷(xiāo)活動(dòng)的話就需要在switch中添加新的類(lèi)型,這對(duì)于開(kāi)發(fā)來(lái)說(shuō)簡(jiǎn)直是災(zāi)難,并且維護(hù)這些代碼也是一個(gè)麻煩。
優(yōu)化一:?jiǎn)我宦氊?zé)原則
上面的代碼中,promotion(…)方法直接完成了所有的工作,但是咋我們實(shí)際實(shí)現(xiàn)中最好讓一個(gè)方法的職責(zé)單一,只完成某一個(gè)功能,所以這里我們將對(duì)折扣類(lèi)型的判斷和計(jì)算價(jià)格分開(kāi):
- 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){
- //計(jì)算使用折扣一后的價(jià)格
- }
- public void calculate2(Order order){
- //計(jì)算使用折扣二后的價(jià)格
- }
- public void calculate3(Order order){
- //計(jì)算使用折扣三后的價(jià)格
- }
- //more calculate
- }
這里我們將折扣類(lèi)型的判斷和計(jì)算價(jià)格分開(kāi),使得promotion(…)方法的代碼量大大降低,提升了代碼的可讀性。面象對(duì)象設(shè)計(jì)6大原則之一:?jiǎn)我宦氊?zé)原則,這篇也推薦大家看下。
優(yōu)化二:策略模式
上面優(yōu)化后的代碼提升了原有代碼的可讀性,但是原來(lái)OrderPromotion類(lèi)代碼大爆炸的問(wèn)題還是沒(méi)有解決。
針對(duì)這個(gè)問(wèn)題,我們希望能夠?qū)⒂?jì)算的代碼和當(dāng)前代碼分離開(kāi),首先我們能想到的就是定義一個(gè)類(lèi),然后將計(jì)算的代碼復(fù)制到這個(gè)類(lèi)中,需要的時(shí)候就調(diào)用。這樣到的確是分離開(kāi)了,但是完全是治標(biāo)不治本。在添加新的促銷(xiāo)活動(dòng)是兩個(gè)類(lèi)都要改。
所以我們希望能夠?qū)⒉煌拇黉N(xiāo)活動(dòng)的實(shí)現(xiàn)分離開(kāi),這樣對(duì)每一種活動(dòng)的實(shí)現(xiàn)都是分開(kāi)的,修改也不會(huì)影響其他的,基于此我們完全可以選擇策略模式來(lái)實(shí)現(xiàn)。
策略模式
策略模式的思想是針對(duì)一組算法,將每一種算法都封裝到具有共同接口的獨(dú)立的類(lèi)中,從而是它們可以相互替換。策略模式的最大特點(diǎn)是使得算法可以在不影響客戶(hù)端的情況下發(fā)生變化,從而改變不同的功能。
- 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();
- }
- }
上面的代碼很明顯已經(jīng)精簡(jiǎn)很多了,到了現(xiàn)在如果需要添加一個(gè)促銷(xiāo)活動(dòng)的話只需定義一個(gè)促銷(xiāo)類(lèi),實(shí)現(xiàn)PromotionCalculation接口然后在switch中添加即可。
優(yōu)化三:工廠模式
上面的代碼雖然已經(jīng)將促銷(xiāo)活動(dòng)的實(shí)現(xiàn)分離開(kāi)了,但是OrderPromotion還是一直在變得,每一次添加或者下線活動(dòng)都需要修改該類(lèi)。
現(xiàn)在我們希望OrderPromotion是不變的,將PromotionCalculation的實(shí)例化剝離開(kāi)來(lái)。創(chuàng)建類(lèi)很明顯是使用工廠設(shè)計(jì)模式了。
OrderPromotion
- public class OrderPromotion {
- public BigDecimal promotion(Order order, int[] promotions){
- for(int promotion:promotions){
- PromotionFactory.getPromotionCalculate(promotion).calculate(order);
- }
- return order.getResultPrice();
- }
- }
類(lèi)的創(chuàng)建工作交給工廠來(lái)實(shí)現(xiàn)。
- 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類(lèi)就不需要改了,每一次添加新的促銷(xiāo)活動(dòng)后只需要在工廠類(lèi)中添加即可。
優(yōu)化四:配置+反射
上面的代碼還存在的問(wèn)題在于每一次需要添加新的促銷(xiāo)活動(dòng)的時(shí)候還是需要修改工廠類(lèi)中的代碼,這里我們通過(guò)配置文件加反射的方式來(lái)解決。
定義映射配置文件
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;
- }
- }
通過(guò)上面的代碼就可以實(shí)現(xiàn)不改變已有代碼的前提下實(shí)現(xiàn)對(duì)功能的靈活擴(kuò)展。當(dāng)然,這里的代碼只是作為演示用的,實(shí)際上可以改進(jìn)的地方還有不少,像最后反射效率較低,也可以通過(guò)其他的方式來(lái)實(shí)現(xiàn)。
小結(jié)
設(shè)計(jì)模式是我們一定要了解的東西,熟悉設(shè)計(jì)模式能讓我們?cè)O(shè)計(jì)出易于擴(kuò)展和維護(hù)的代碼結(jié)構(gòu)。但是并不是任何地方都需要上設(shè)計(jì)模式,應(yīng)該結(jié)合我們的項(xiàng)目實(shí)際進(jìn)行分析是否需要設(shè)計(jì)模式,使用哪種設(shè)計(jì)模式。