設計模式:與SpringMVC底層息息相關的適配器模式
- 2019 年 10 月 3 日
- 筆記
前言
適配器模式是最為普遍的設計模式之一,它不僅廣泛應用於程式碼開發,在日常生活里也很常見。比如筆記型電腦上的電源適配器,可以使用在110~ 220V之間變化的電源,而筆記型電腦還能正常工作,這就是適配器模式最直接的例子,同時也是其思想的體現,簡單的說,適配器模式就是把一個類(介面)轉換成其他的類(介面)。
適配器模式
1、定義
適配器模式,也叫包裝模式,指的是將一個類的介面變換成客戶端所期待的另一種介面,從而使原本因介面不匹配而無法在一起工作的兩個類能夠在一起工作。我們可以通過增加一個適配器類來解決介面不兼容的問題,而這個適配器類就相當於筆記型電腦的適配器。
根據適配器類與適配者類的關係不同,適配器模式可分為對象適配器和類適配器兩種,在對象適配器模式中,適配器與適配者之間是關聯關係;在類適配器模式中,適配器與適配者之間是繼承(或實現)關係。
2、UML類圖
適配器模式包含了幾個角色,它們是:
Target(目標角色):該角色定義其他類轉化成何種介面,可以是一個抽象類或介面,也可以是具體類。
Adaptee(源角色):你想把誰轉換成目標角色,這個「誰」就是源角色,它是已經存在的、運行良好的類或對象。
Adapter(適配器角色):適配器模式的核心角色,職責就是通過繼承或是類關聯的方式把源角色轉換為目標角色。
3、實戰例子
知道有哪些角色和UML類圖後,我們就可以寫一下程式碼了,為了方便理解,我們用生活中充電器的例子來講解適配器,現在有一個手機要充電,所需要的額定電壓是5V,而家用交流電的電壓是標準的220V,這種情況下要充電就需要有個適配器來做電壓轉換。
把三者代入我們上面畫的類圖不難得出,充電器本身相當於Adapter,220V交流電相當於Adaptee,我們的目標Target是5V直流電。
Target目標介面
public interface Voltage5 { int output5V(); }
目標介面的實現類
public class ConcreteVoltage5 implements Voltage5{ @Override public int output5V() { int src = 5; System.out.println("目標電壓:" + src + "V"); return src; } }
Adaptee類
public class Voltage220 { // 輸出220V的電壓 public int output220V() { int src = 220; System.out.println("源電壓是:" + src + "V"); return src; } }
Adapter類:完成220V-5V的轉變
public class VoltageAdapter extends Voltage220 implements Voltage5 { @Override public int output5V() { // 獲取到源電壓 int adaptee = super.output220V(); // 開始適配操作 System.out.println("對象適配器工作,開始適配電壓"); int dst = adaptee / 44; System.out.println("適配完成後輸出電壓:" + dst + "V"); return dst; } }
通過適配器類的轉換,我們就可以把220V的電壓轉成我們需要的5V電壓了,寫個場景類測試下:
/** * 適配器模式 */ public class Client { public static void main(String[] args) { Voltage5 voltage5 = new ConcreteVoltage5(); voltage5.output5V(); // 創建一個適配器對象 VoltageAdapter2 voltageAdapter = new VoltageAdapter2(); // 轉換電壓 voltageAdapter.output5V(); } }
結果輸出
目標電壓:5V 源電壓是:220V 對象適配器工作,開始適配電壓 適配完成後輸出電壓:5V
前面說了,適配器模式分為兩種,我們上面介紹的通過繼承的方式實現的是類適配器,還有一種對象適配器,它是通過關聯適配器與適配者的方式實現的,它的通用類圖如下所示:
程式碼方面也比較簡單,參考上面的例子把繼承的寫法改為關聯即可,程式碼就不貼了,讀者可以自己寫一下。
4、總結
下面對適配器模式做一下總結吧
1)優點
- 能實現目標類和願角色類的解耦。適配器模式可以讓兩個沒有任何關係的類在一起運行,只要適配器這個角色能夠搞定他們就成。
- 增加了類的透明性。將具體的業務實現過程封裝在適配者類中,對於客戶端類而言是透明的,而且提高了適配者的復用性,同一個適配者類可以在多個不同的系統中復用。
- 靈活性和擴展性都非常好。某一天,突然不想要適配器,沒問題,刪除掉這個適配器就可以了,其他的程式碼都不用 修改,基本上就類似一個靈活的構件,想用就用,不想就卸載。
2)缺點
-
類適配器:採用繼承方式,對Java這種不支援多繼承的語言來說,一次只能適配一個適配器類,不太方便
-
對象適配器:與類適配器模式相比,要在適配器中置換適配者類的某些方法比較麻煩。如果一定要置換掉適配者類的一個或多個方法,可以先做一個適配者類的子類,將適配者類的方法置換掉,然後再把適配者類的子類當做真正的適配者進行適配,實現過程較為複雜。
3)適用場景
- 需要修改到已上線介面時,適配器模式可能是最適合的模式。
- 系統擴展了,需要使用一個已有或新建的類,但類不符合系統的介面支援,這時就可以引入適配器類來做轉換。
SpringMVC底層的適配器模式
這裡擴展一下適配器模式的知識點,想必做過Java開發的都知道SpringMVC,這套框架可以幫助我們把前端的請求訪問到後台對應的controller的方法上,然後再把處理結果返回給後端,它的底層其實就用到了適配器模式。
SpringMVC中的適配器模式主要用於執行目標Controller中的請求處理方法。在它的底層處理中,DispatcherServlet作為用戶,HandlerAdapter作為期望介面,具體的適配器實現類用於對目標類進行適配,Controller作為需要適配的類。
為什麼要在 Spring MVC 中使用適配器模式?Spring MVC 中的 Controller 種類眾多,不同類型的 Controller 通過不同的方法來對請求進行處理。如果不利用適配器模式的話,DispatcherServlet 直接獲取對應類型的 Controller,那樣每增加一個類型的Controller就需要使用增加一個if else判斷instance of,這違反了設計模式的開閉原則 —— 對擴展開放,對修改關閉。
那麼SpringMVC是怎麼處理的呢?我們來簡單看一下源碼,首先是適配器介面HandlerAdapter,
public interface HandlerAdapter { boolean supports(Object var1); ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception; long getLastModified(HttpServletRequest var1, Object var2); }
該介面的適配器類對應著 Controller,每自定義一個Controller需要定義一個實現HandlerAdapter的適配器。舉個例子,有一個適配器類HttpRequestHandlerAdapter
,該類就是實現了HandlerAdapter介面,這是它的源碼:
public class HttpRequestHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof HttpRequestHandler); } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((HttpRequestHandler) handler).handleRequest(request, response); return null; } @Override public long getLastModified(HttpServletRequest request, Object handler) { if (handler instanceof LastModified) { return ((LastModified) handler).getLastModified(request); } return -1L; } }
當Spring容器啟動後,會將所有定義好的適配器對象存放在一個List集合中,當一個請求來臨時,DispatcherServlet會通過handler的類型找到對應適配器,並將該適配器對象返回給用戶,然後就可以統一通過適配器的handle
方法來調用Controller中的用於處理請求的方法。
public class DispatcherServlet extends FrameworkServlet { protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { //......................... //找到匹配當前請求對應的適配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); }finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } } // 遍歷集合,找到合適的匹配器 protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { for (HandlerAdapter ha : this.handlerAdapters) { if (logger.isTraceEnabled()) { logger.trace("Testing handler adapter [" + ha + "]"); } if (ha.supports(handler)) { return ha; } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); } }
這樣一來,所有的controller就都統一交給HandlerAdapter處理,免去了大量的 if-else 語句判斷,同時增加controller類型只需增加一個適配器即可,不需要修改到Servlet的邏輯,符合開閉原則。
關於適配器模式的介紹就到這裡了,有不足之處還望指出。
參考
《設計模式之禪》
https://blog.csdn.net/wwwdc1012/article/details/82780560