别再用if-else了,用注解去代替他吧
策略模式
經常在網上看到一些名為“別再if-else走天下了”,“教你干掉if-else”等之類的文章,大部分都會講到用策略模式去代替if-else。策略模式實現的方式也大同小異。主要是定義統一行為(接口或抽象類),并實現不同策略下的處理邏輯(對應實現類)。客戶端使用時自己選擇相應的處理類,利用工廠或其他方式。
注解實現
本文要說的是用注解實現策略模式的方式,以及一些注意點。
話不多說,還是以最常 見的訂單處理為例。首先定義這樣一個訂單實體類:
假如對于不同來源(pc端、移動端)的訂單需要不同的邏輯處理。項目中一般會有OrderService這樣一個類,如下,里面有一坨if-else的邏輯,目的是根據訂單的來源的做不同的處理。
@Service public?class?OrderService?{public?void?orderService(Order?order)?{if(order.getSource().equals("pc")){//?處理pc端訂單的邏輯}else?if(order.getSource().equals("mobile")){//?處理移動端訂單的邏輯}else?{//?其他邏輯}} }策略模式就是要干掉上面的一坨if-else,使得代碼看起來優雅且高大上。
現在就讓我們開始干掉這一坨if-else。先總覽下結構:
1.首先定義一個OrderHandler接口,此接口規定了處理訂單的方法。
2.定義一個OrderHandlerType注解,來表示某個類是用來處理何種來源的訂單。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Service public?@interface?OrderHandlerType?{String?source(); }3.接下來就是實現pc端和移動端訂單處理各自的handler,并加上我們所定義的OrderHandlerType注解。
@OrderHandlerType(source?=?"mobile") public?class?MobileOrderHandler?implements?OrderHandler?{@Overridepublic?void?handle(Order?order)?{System.out.println("處理移動端訂單");} }@OrderHandlerType(source?=?"pc") public?class?PCOrderHandler?implements?OrderHandler?{@Overridepublic?void?handle(Order?order)?{System.out.println("處理PC端訂單");} }4.以上準備就緒后,就是向spring容器中注入各種訂單處理的handler,并在OrderService.orderService方法中,通過策略(訂單來源)去決定選擇哪一個OrderHandler去處理訂單。我們可以這樣做:
@Service public?class?OrderService?{private?Map<String,?OrderHandler>?orderHandleMap;@Autowiredpublic?void?setOrderHandleMap(List<OrderHandler>?orderHandlers)?{//?注入各種類型的訂單處理類orderHandleMap?=?orderHandlers.stream().collect(Collectors.toMap(orderHandler?->?AnnotationUtils.findAnnotation(orderHandler.getClass(),?OrderHandlerType.class).source(),v?->?v,?(v1,?v2)?->?v1));}public?void?orderService(Order?order)?{//?...一些前置處理//?通過訂單來源確定對應的handlerOrderHandler?orderHandler?=?orderHandleMap.get(order.getSource());orderHandler.handle(order);//?...一些后置處理} }在OrderService中,維護了一個orderHandleMap,它的key為訂單來源,value為對應的訂單處理器Handler。通過@Autowired去初始化orderHandleMap(這里有一個lambda表達式,仔細看下其實沒什么難度的)。這樣一來,OrderService.orderService里的一坨if-else不見了,取而代之的僅僅是兩行代碼。即,先從orderHandleMap中根據訂單來源獲取對應的OrderHandler,然后執行OrderHandler.handle方法即可。
這種做法的好處是,不論以后業務如何發展致使訂單來源種類增加,OrderService的核心邏輯不會改變,我們只需要實現新增來源的OrderHandler即可,且團隊中每人開發各自負責的訂單來源對應的OrderHandler即可,彼此間互不干擾。
到此,似乎已經講完了通過注解實現策略模式,干掉if-else的方法,就這樣結束了嗎?不,真正的重點從現在才開始。
現在回過頭看orderHandleMap這個Map,它的key是訂單來源,假如,我們想通過訂單來源+訂單支付方式這兩個屬性來決定到底使用哪一種OrderHandler怎么辦?比如PC端支付寶支付的訂單是一種處理邏輯(PCAliPayOrderHandler),PC端微信支付的訂單是另外一種處理邏輯(PCWeChatOrderHandler),對應的還有移動端支付寶支付(MobileAliPayOrderHandler)和移動端微信支付(MobileWeChatOrderHandler)。
這時候我們的key應該存什么呢,可能有人會說,我直接存訂單來源+訂單支付方式組成的字符串不就行了嗎?確實可以,但是如果這時業務邏輯又變了(向pm低頭),PC端支付寶支付和微信支付是同一種處理邏輯,而移動端支付寶支付和微信支付是不同的處理邏輯,那情況就變成了PCAliPayOrderHandler和PCWeChatOrderHandler這兩個類是同一套代碼邏輯。我們干掉了if-else,但卻造出了兩份相同的代碼,這是一個作為有強迫癥的程序員所不能容忍的。怎么干掉這兩個邏輯相同的類呢?
首先,我們可以回顧下,注解它究竟是個什么玩意?不知道大家有沒有注意到定義注解的語法,也就是@interface,與定義接口的語法想比,僅僅多了一個@。翻看jdk,可以找到這么一個接口Annotation,如下
/***?The?common?interface?extended?by?all?annotation?types.??Note?that?an*?interface?that?manually?extends?this?one?does?<i>not</i>?define*?an?annotation?type.??Also?note?that?this?interface?does?not?itself*?define?an?annotation?type.**?More?information?about?annotation?types?can?be?found?in?p?9.6?of*?<cite>The?Java™?Language?Specification</cite>.**?The?{@link?java.lang.reflect.AnnotatedElement}?interface?discusses*?compatibility?concerns?when?evolving?an?annotation?type?from?being*?non-repeatable?to?being?repeatable.**?@author??Josh?Bloch*?@since???1.5*/ public?interface?Annotation?{//?…省略 }開頭就表明了,The common interface extended by all annotation types。說的很明白了,其實注解它就是個接口,對,它就是個接口而已,@interface僅僅是個語法糖。那么,注解既然是個接口,就必然會有相應的實現類,那實現類哪里來呢?上述中我們僅僅定義了OrderHandlerType注解,別的什么也沒有做。這時候不得不提動態代理了,一定是jdk在背后為我們做了些什么。
為了追蹤JVM在運行過程中生成的JDK動態代理類。我們可以設置VM啟動參數如下:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true該參數可以保存所生成的JDK動態代理類到本地。額外說一句,若我們想追蹤cglib所生成的代理類,即對應的字節碼文件,可以設置參數:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,?"保存的路徑");添加參數后再次啟動項目,可以看到我們項目里多了許多class文件(這里只截取了部分,筆者啟動的項目共生成了97個動態代理類。由于我的項目是springboot環境,因此生成了許多如ConditionalOnMissingBean、Configuration、Autowired等注解對應的代理類):
那接下來就是要找到我們所關心的,即jdk為我們自定義的OrderHandlerType注解所生成的代理類。由于jdk生成的動態代理類都會繼承Proxy這個類,而java又是單繼承的,所以,我們只需要找到實現了OrderHandlerType的類即可。遍歷這些類文件,發現$Proxy63.class實現了我們定義的OrderHandlerType注解:
public?final?class?$Proxy63?extends?Proxy?implements?OrderHandlerType?{private?static?Method?m1;private?static?Method?m2;private?static?Method?m4;private?static?Method?m3;private?static?Method?m0;public?$Proxy63(InvocationHandler?var1)?throws??{super(var1);}//?…省略 }我們知道,jdk動態代理其實現的核心是:
也就是這個構造函數的InvocationHandler對象了。那么注解的InvocationHandler是哪個具體實現呢?不難發現就是:
這個類里面的核心屬性,就是那個 memberValues,我們在使用注解時給注解屬性的賦值,都存儲在這個map里了。而代理類中的各種方法的實現,實際上是調用了 AnnotationInvocationHandler 里的 invoke 方法。
好了,現在我們知道了注解就是個接口,且通過動態代理實現其中所定義的各種方法。那么回到我們的OrderService,為什么不把key的類型設置為OrderHandlerType?就像這樣。
private?Map<OrderHandlerType,?OrderHandler>?orderHandleMap;如此一來,不管決定訂單處理器orderhandler的因素怎么變,我們便可以以不變應萬變(這不就是我們所追求的代碼高擴展性和靈活性么)。那當我們的map的key變成了OrderHandlerType之后,注入和獲取的邏輯就要相應改變,注入的地方很好改變,如下:
public?class?OrderService?{private?Map<OrderHandlerType,?OrderHandler>?orderHandleMap;@Autowiredpublic?void?setOrderHandleMap(List<OrderHandler>?orderHandlers)?{//?注入各種類型的訂單處理類orderHandleMap?=?orderHandlers.stream().collect(Collectors.toMap(orderHandler?->?AnnotationUtils.findAnnotation(orderHandler.getClass(),?OrderHandlerType.class),v?->?v,?(v1,?v2)?->?v1));}//?...省略 }那獲取的邏輯要怎么實現?我們怎么根據order的來源和支付方式去orderHandleMap里獲取對應的OrderHandler呢?問題變成了如何關聯order的來源和支付方式與OrderHandlerType注解。
還記得剛才所說的注解就是個接口嗎,既然是個接口,我們自己實現一個類不就完事了么,這樣就把order的來源和支付方式與OrderHandlerType注解關聯起來了。說干就干,現在我們有了這么一個類,
public?class?OrderHandlerTypeImpl?implements?OrderHandlerType?{private?String?source;private?String?payMethod;OrderHandlerTypeImpl(String?source,?String?payMethod)?{this.source?=?source;this.payMethod?=?payMethod;}@Overridepublic?String?source()?{return?source;}@Overridepublic?String?payMethod()?{return?payMethod;}@Overridepublic?Class<??extends?Annotation>?annotationType()?{return?OrderHandlerType.class;}}在獲取對應OrderHandler時我們可以這樣寫,
public?void?orderService(Order?order)?{//?...一些前置處理//?通過訂單來源確以及支付方式獲取對應的handlerOrderHandlerType?orderHandlerType?=?new?OrderHandlerTypeImpl(order.getSource(),?order.getPayMethod());OrderHandler?orderHandler?=?orderHandleMap.get(orderHandlerType);orderHandler.handle(order);//?...一些后置處理 }看起來沒什么問題了,來運行一下。不對勁啊,空指針,那個異常它來了。
我們斷點打在NPE那一行,
一定是姿勢不對,漏掉了什么。那我們就來分析下。orderHandleMap中確實注入了所定義的幾個OrderHandler(PCAliPayOrderHandler、PCWeChatOrderHandler、MobileAliPayOrderHandler、MobileWeChatOrderHandler),但是get卻沒有獲取到,這是為什么呢?我們來回憶下,map的get方法邏輯,還記得最開始學習java時的hashCode和equals方法嗎?
不知道大家注意到沒有,map中key對應的類型是$Proxy63(jdk動態代理給我們生成的),跟我們自己實現的OrderHandlerTypeImpl是不同類型的。
梳理下,在Autowied時,我們放進orderHandleMap的key是動態代理類對象,而獲取時,自定義了OrderHandlerTypeI實現類OrderHandlerTypeImpl,而又沒有重寫hashCode和equals方法,才導致從map中沒有獲取到我們所想要的OrderHandler,那么,我們把實現類OrderHandlerTypeImpl的hashCode和equals這兩個方法重寫,保持跟動態代理類生成的一樣不就行了嗎?
再回看下動態代理給我們生成的這兩個方法,前面說了,注解對應的代理類方法調用實際上都是AnnotationInvocationHandler里面的方法,翻看AnnotationInvocationHandler里面的hashCode和equals方法:
具體的邏輯也比較簡單,就不分析了。那我們就按照AnnotationInvocationHandler中的實現,在我們的OrderHandlerTypeImpl中按照相同的邏輯重寫下這兩個方法,如下
public?class?OrderHandlerTypeImpl?implements?OrderHandlerType?{//?…省略@Overridepublic?int?hashCode()?{int?hashCode?=?0;hashCode?+=?(127?*?"source".hashCode())?^?source.hashCode();hashCode?+=?(127?*?"payMethod".hashCode())?^?payMethod.hashCode();return?hashCode;}@Overridepublic?boolean?equals(Object?obj)?{if?(!(obj?instanceof?OrderHandlerType))?{return?false;}OrderHandlerType?other?=?(OrderHandlerType)?obj;return?source.equals(other.source())?&&?payMethod.equals(other.payMethod());} }再次運行看看是否達到我們預期,果不其然,這次可以正常獲取到了handler,至此,大功告成。
這樣以來,不管以后業務怎么發展,OrderService核心邏輯不會改變,只需要擴展OrderHandler即可。
?
直面Java第329期:哪個命令可以監控虛擬機各種運行狀態信息?
深入并發第013期:拓展synchronized——鎖優化
如果你喜歡本文,
請長按二維碼,關注?Hollis.
轉發至朋友圈,是對我最大的支持。
點個?在看?
喜歡是一種感覺
在看是一種支持
↘↘↘
總結
以上是生活随笔為你收集整理的别再用if-else了,用注解去代替他吧的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringSecurity自定义Use
- 下一篇: 2019年退税可以开始申报了 | 一笔3