設計模式:與SpringMVC底層息息相關的適配器模式

前言

適配器模式是最爲廣泛的設計模式之一,它不只普遍應用於代碼開發,在平常生活裏也很常見。好比筆記本上的電源適配器,可使用在110~ 220V之間變化的電源,而筆記本還能正常工做,這就是適配器模式最直接的例子,同時也是其思想的體現,簡單的說,適配器模式就是把一個類(接口)轉換成其餘的類(接口)。前端

適配器模式

一、定義

適配器模式,也叫包裝模式,指的是**將一個類的接口變換成客戶端所期待的另外一種接口,從而使本來因接口不匹配而沒法在一塊兒工做的兩個類可以在一塊兒工做。**咱們能夠經過增長一個適配器類來解決接口不兼容的問題,而這個適配器類就至關於筆記本的適配器。後端

根據適配器類與適配者類的關係不一樣,適配器模式可分爲對象適配器和類適配器兩種,在對象適配器模式中,適配器與適配者之間是關聯關係;在類適配器模式中,適配器與適配者之間是繼承(或實現)關係。設計模式

二、UML類圖

適配器模式包含了幾個角色,它們是:

Target(目標角色):該角色定義其餘類轉化成何種接口,能夠是一個抽象類或接口,也能夠是具體類。bash

Adaptee(源角色):你想把誰轉換成目標角色,這個「誰」就是源角色,它是已經存在的、運行良好的類或對象。app

Adapter(適配器角色):適配器模式的核心角色,職責就是經過繼承或是類關聯的方式把源角色轉換爲目標角色。框架

三、實戰例子

知道有哪些角色和UML類圖後,咱們就能夠寫一下代碼了,爲了方便理解,咱們用生活中充電器的例子來說解適配器,如今有一個手機要充電,所須要的額定電壓是5V,而家用交流電的電壓是標準的220V,這種狀況下要充電就須要有個適配器來作電壓轉換。async

把三者代入咱們上面畫的類圖不可貴出,充電器自己至關於Adapter,220V交流電至關於Adaptee,咱們的目標Target是5V直流電。ide

Target目標接口測試

public interface Voltage5 {
	
    int output5V();
}
複製代碼

目標接口的實現類ui

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
複製代碼

前面說了,適配器模式分爲兩種,咱們上面介紹的經過繼承的方式實現的是類適配器,還有一種對象適配器,它是經過關聯適配器與適配者的方式實現的,它的通用類圖以下所示:

代碼方面也比較簡單,參考上面的例子把繼承的寫法改成關聯便可,代碼就不貼了,讀者能夠本身寫一下。

四、總結

下面對適配器模式作一下總結吧

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的邏輯,符合開閉原則。

關於適配器模式的介紹就到這裏了,有不足之處還望指出。

參考

《設計模式之禪》

blog.csdn.net/wwwdc1012/a…

相關文章
相關標籤/搜索