第八章:介面卡模式

禿然變可愛的小貓咪發表於2020-12-07

1、介面卡模式基本介紹

現實生活中的介面卡例子

泰國旅遊使用插座問題:泰國插座用的是兩孔的(歐標) ,可以買個多功能轉換插頭 (介面卡) ,這樣就可以使用了

在這裡插入圖片描述

介面卡模式基本介紹

介面卡模式(Adapter Pattern)將某個類的介面轉換成客戶端期望的另一個介面表示,主的目的是相容性,讓原本因介面不匹配不能一起工作的兩個類可以協同工作。
介面卡的別名為包裝器(Wrapper),介面卡模式屬於結構型模式
主要分為三類:類介面卡模式、物件介面卡模式、介面介面卡模式

2、介面卡模式工作原理

  1. 介面卡模式:將一個類的介面轉換成另一種介面,讓原本介面不相容的類可以相容
  2. 從使用者的角度看不到被適配者,使用者與被適配者是解耦的
  3. 使用者呼叫介面卡轉化出來的目標介面方法, 介面卡再呼叫被適配者的相關介面方法
  4. 使用者收到反饋結果,感覺只是和目標介面互動, 如圖
    在這裡插入圖片描述

3、類介面卡模式

類介面卡模式介紹

基本介紹: 核心模組是 Adapter 類,Adapter 類,通過繼承 src 類(被適配者),實現 dst 類介面(目標類),完成 src --> dst 的適配

類介面卡模式應用例項
應用例項說明

以生活中充電器的例子來講解介面卡,充電器本身相當於Adapter, 220V交流電相當於src (即被適配者), 我們的dst(即目標)是5V直流電

在這裡插入圖片描述
類圖

在這裡插入圖片描述
程式碼實現

Voltage220V:src類,輸出 220V 的電壓

//被適配的類
public class Voltage220V {
	// 輸出220V的電壓
	public int output220V() {
		int src = 220;
		return src;
	}
}

IVoltage5V:介面卡介面(dst 介面),規定介面卡的規範

//適配介面
public interface IVoltage5V {
	public int output5V();
}

VoltageAdapter:介面卡,繼承了 Voltage220V 並實現了 IVoltage5V 介面

//介面卡類
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
	@Override
	public int output5V() {
		int srcV = output220V(); // 獲取到220V電壓
		int dstV = srcV / 44; // 降壓轉成 5v
		return dstV;
	}
}

Phone:使用 5V 介面卡進行充電

public class Phone {
	// 充電
	public void charging(IVoltage5V iVoltage5V) {
		if (iVoltage5V.output5V() == 5) {
			System.out.println("電壓為5V, 可以充電~~");
		} else if (iVoltage5V.output5V() > 5) {
			System.out.println("電壓大於5V, 不能充電~~");
		}
	}
}

Client:客戶端,進行程式碼測試

public class Client {

	public static void main(String[] args) {
		System.out.println(" === 類介面卡模式 ====");
		Phone phone = new Phone();
		phone.charging(new VoltageAdapter());
	}

}

總結

Voltage220V 只能輸出 220V 的電壓,我們定義一個抽象的介面卡規範:IVoltage5V 介面,該介面裡面有一個抽象方法 public int output5V();,介面卡 VoltageAdapter 繼承 Voltage220V 並實現 IVoltage5V 介面,可以將 220V 的電壓轉為 5V 電壓

類介面卡模式注意事項和細節

缺點

Java是單繼承機制,所以類介面卡需要繼承src類這一點算是一個缺點,因為Adapter已經繼承了src類,這要求dst必須是介面,有一定侷限性

src類的方法在Adapter中都會暴露出來,也增加了使用的成本,因為src類中的方法可能很多

4、物件介面卡模式

物件介面卡模式介紹
  1. 基本思路和類的介面卡模式相同,只是將Adapter類作修改,不是繼承src類,而是持有src類的例項,以解決相容性的問題。
  2. 物件介面卡模式的核心思想:介面卡持有src類,實現dst類介面,完成src --> dst的適配
  3. 根據“合成複用原則”,在系統中儘量使用關聯關係(聚合、組合)來替代繼承關係。
  4. 物件介面卡模式是介面卡模式常用的一種
物件介面卡模式應用例項

應用例項說明

以生活中充電器的例子來講解介面卡,充電器本身相當於Adapter, 220V交流電相當於src (即被適配者), 我們的dst(即目標)是5V直流電, 使用物件介面卡模式完成


在這裡插入圖片描述
程式碼實現

  1. Voltage220V:src類,輸出 220V 的電壓,與類介面卡中的程式碼一樣

  2. IVoltage5V:介面卡介面(dst 介面),規定介面卡的規範,與類介面卡中的程式碼一樣

  3. VoltageAdapter:介面卡,VoltageAdapter 中聚合了一個 Voltage220V 類的物件,並實現了 IVoltage5V 介面

//介面卡類
public class VoltageAdapter implements IVoltage5V {

	private Voltage220V voltage220V; // 關聯關係-聚合

	// 通過構造器,傳入一個 Voltage220V 例項
	public VoltageAdapter(Voltage220V voltage220v) {
		this.voltage220V = voltage220v;
	}

	@Override
	public int output5V() {
		int dst = 0;
		if (null != voltage220V) {
			int src = voltage220V.output220V();// 獲取220V 電壓
			System.out.println("使用物件介面卡,進行適配~~");
			dst = src / 44;
			System.out.println("適配完成,輸出的電壓為=" + dst);
		}
		return dst;
	}

}

Phone:使用 5V 介面卡進行充電,與類介面卡中的程式碼一樣

Client:客戶端,進行程式碼測試,建立介面卡 VoltageAdapter 時,注入 src 類例項:new Voltage220V()

public class Client {

	public static void main(String[] args) {
		System.out.println(" === 物件介面卡模式 ====");
		Phone phone = new Phone();
		phone.charging(new VoltageAdapter(new Voltage220V()));
	}

}

總結

與類介面卡模式相比,物件介面卡模式中,介面卡 Adapter 沒有使用繼承關係,而是使用聚合關係,在介面卡 Adapter 中聚合了一個 src 類例項,相同的是 Adapter 實現介面卡介面(dst介面),在 Adapter 中實現 dst 介面中的抽象方法,然後使用 src 類例項和完成適配(轉換)

物件介面卡模式注意事項和細節

物件介面卡和類介面卡其實算是同一種思想,只不過實現方式不同。根據合成複用原則, 使用組合替代繼承, 所以它解決了類介面卡必須繼承src的侷限性問題,也不再要求dst必須是介面。物件介面卡的使用成本更低,更靈活

5、介面介面卡模式

介面介面卡模式介紹

  1. 一些書籍稱為:介面卡模式(Default Adapter Pattern)或預設介面卡模式
  2. 核心思想:當不需要全部實現介面提供的方法時,可先設計一個抽象類實現介面,併為該介面中每個方法提供一個預設實現(空方法),那麼該抽象類的子類可有選擇地覆蓋父類的某些方法來實現需求
  3. 適用於一個介面的實現類不想使用其所有的方法的情況

介面介面卡模式應用例項
類圖

在這裡插入圖片描述

程式碼示例

Interface4:介面

public interface Interface4 {
	public void m1();

	public void m2();

	public void m3();

	public void m4();
}

AbsAdapter:介面卡,對介面中的抽象方法進行空實現

//在AbsAdapter 我們將 Interface4 的方法進行預設實現
public abstract class AbsAdapter implements Interface4 {
	// 預設實現
	public void m1() {

	}

	public void m2() {

	}

	public void m3() {

	}

	public void m4() {

	}
}

Client:客戶端

public class Client {
	public static void main(String[] args) {

		AbsAdapter absAdapter = new AbsAdapter() {
			// 只需要去覆蓋我們 需要使用 介面方法
			@Override
			public void m1() {
				System.out.println("使用了m1的方法");
			}
		};

		absAdapter.m1();
	}
}

6、SpringMVC 原始碼

介面卡模式在SpringMVC框架應用的原始碼剖析

SpringMVC中的HandlerAdapter,就使用了介面卡模式,SpringMVC處理請求的流程回顧:

  1. 首先使用者請求到達前端控制器 dispatcherServlet 的 doDispatch() 方法
  2. 在 doDispatch() 中,通過 HandlerMapping 找到使用者請求的 Handler(處理器)
  3. 通過 Handler 執行目標方法,獲得本次訪問結果:ModelAndView 物件
  4. 接著呼叫 InternalResourceViewResolve 對返回的 ModelAndView 物件進行解析,找到指定的資源
  5. 目標資源(JSP 頁面或者 JSON 字串)最終都會以 JSON 字串的形式返回給 Tomcat
  6. Tomcat 將字串 以 HTTP 協議的方式返回給瀏覽器

使用 HandlerAdapter 的原因分析:

可以看到處理器的型別不同,有多種實現方式,那麼呼叫方式就不是確定的,如果需要直接呼叫
Controller方法,需要呼叫的時候就得不斷是使用if else來進行判斷是哪一種子類然後執行。那麼如果後面要擴充套件Controller,就得修改原來的程式碼,這樣違背了OCP 原則

原始碼追蹤

doDispatch() 方法

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

	try {
		ModelAndView mv = null;
		Exception dispatchException = null;

		try {
			processedRequest = checkMultipart(request);
			multipartRequestParsed = processedRequest != request;

			// Determine handler for the current request.
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null || mappedHandler.getHandler() == null) {
				noHandlerFound(processedRequest, response);
				return;
			}

			// Determine handler adapter for the current request.
			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;
				}
			}

			applyDefaultViewName(request, mv);
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		catch (Exception ex) {
			dispatchException = ex;
		}
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	catch (Exception ex) {
		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
	}
	catch (Error err) {
		triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
	}
	finally {
		if (asyncManager.isConcurrentHandlingStarted()) {
			// Instead of postHandle and afterCompletion
			mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
			return;
		}
		// Clean up any resources used by a multipart request.
		if (multipartRequestParsed) {
			cleanupMultipart(processedRequest);
		}
	}
}

getHandlerAdapter() 方法

/**
 * Return the HandlerAdapter for this handler object.
 * @param handler the handler object to find an adapter for
 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
 */
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");
}

HandlerAdapter 只是一個介面,定義了介面卡的規範

public interface HandlerAdapter {

	/**
	 * Given a handler instance, return whether or not this {@code HandlerAdapter}
	 * can support it. Typical HandlerAdapters will base the decision on the handler
	 * type. HandlerAdapters will usually only support one handler type each.
	 * <p>A typical implementation:
	 * <p>{@code
	 * return (handler instanceof MyHandler);
	 * }
	 * @param handler handler object to check
	 * @return whether or not this object can use the given handler
	 */
	boolean supports(Object handler);

	/**
	 * Use the given handler to handle this request.
	 * The workflow that is required may vary widely.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @param handler handler to use. This object must have previously been passed
	 * to the {@code supports} method of this interface, which must have
	 * returned {@code true}.
	 * @throws Exception in case of errors
	 * @return ModelAndView object with the name of the view and the required
	 * model data, or {@code null} if the request has been handled directly
	 */
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

	/**
	 * Same contract as for HttpServlet's {@code getLastModified} method.
	 * Can simply return -1 if there's no support in the handler class.
	 * @param request current HTTP request
	 * @param handler handler to use
	 * @return the lastModified value for the given handler
	 * @see javax.servlet.http.HttpServlet#getLastModified
	 * @see org.springframework.web.servlet.mvc.LastModified#getLastModified
	 */
	long getLastModified(HttpServletRequest request, Object handler);

}

HandlerAdapter 繼承樹

在這裡插入圖片描述
原始碼分析與總結

首先,我們拿到此次請求的 Request 物件:HttpServletRequest processedRequest = request;

接著,通過 Request 物件拿到 Handler(Controller) 物件:mappedHandler = getHandler(processedRequest);

然後通過 Handler 拿到對應的介面卡( Adapter):HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

最後通過介面卡呼叫 Controller 的方法並返回 ModelAndView:mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

動手寫 SpringMVC 通過介面卡設計模式

在這裡插入圖片描述
程式碼實現
Controller 介面及其實現類

//多種Controller實現  
public interface Controller {

}

class HttpController implements Controller {
	public void doHttpHandler() {
		System.out.println("http...");
	}
}

class SimpleController implements Controller {
	public void doSimplerHandler() {
		System.out.println("simple...");
	}
}

class AnnotationController implements Controller {
	public void doAnnotationHandler() {
		System.out.println("annotation...");
	}
}


HandlerAdapter 介面及其實現類

//定義一個Adapter介面 
public interface HandlerAdapter {
	// 當前 HandlerAdapter 物件是否支援 handler(判斷 handler 的型別是否為具體的子類型別)
	public boolean supports(Object handler);

	// 執行目標方法(將 handler 物件強轉後,呼叫對應的方法)
	public void handle(Object handler);
}

// 多種介面卡類
class SimpleHandlerAdapter implements HandlerAdapter {

	public void handle(Object handler) {
		((SimpleController) handler).doSimplerHandler();
	}

	public boolean supports(Object handler) {
		return (handler instanceof SimpleController);
	}

}

class HttpHandlerAdapter implements HandlerAdapter {

	public void handle(Object handler) {
		((HttpController) handler).doHttpHandler();
	}

	public boolean supports(Object handler) {
		return (handler instanceof HttpController);
	}

}

class AnnotationHandlerAdapter implements HandlerAdapter {

	public void handle(Object handler) {
		((AnnotationController) handler).doAnnotationHandler();
	}

	public boolean supports(Object handler) {
		return (handler instanceof AnnotationController);
	}

}

DispatchServlet:模擬 doDispatch() 方法中獲取介面卡的流程

public class DispatchServlet {

	public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();

	// 組合了多個 HandlerAdapter 的實現類
	public DispatchServlet() {
		handlerAdapters.add(new AnnotationHandlerAdapter());
		handlerAdapters.add(new HttpHandlerAdapter());
		handlerAdapters.add(new SimpleHandlerAdapter());
	}

	public void doDispatch() {

		// 此處模擬SpringMVC從request取handler的物件,
		// 介面卡可以獲取到希望的Controller
		HttpController controller = new HttpController();
		// AnnotationController controller = new AnnotationController();
		// SimpleController controller = new SimpleController();
		
		// 得到對應介面卡
		HandlerAdapter adapter = getHandler(controller);
		// 通過介面卡執行對應的controller對應方法
		adapter.handle(controller);

	}

	public HandlerAdapter getHandler(Controller controller) {
		// 遍歷:根據得到的controller(handler), 返回對應介面卡
		for (HandlerAdapter adapter : this.handlerAdapters) {
			if (adapter.supports(controller)) {
				return adapter;
			}
		}
		return null;
	}

	public static void main(String[] args) {
		new DispatchServlet().doDispatch(); // http...
	}

}

總結

HandlerAdapter 的作用:
public boolean supports(Object handler);:當前介面卡是否支援 handler,從上面原始碼可以看出,使用 instanceof 關鍵字進行判斷
public void handle(Object handler);:執行 Handler(Controller) 的目標方法,即 HandlerAdapter 代替原有的 Handler(Controller) 執行目標方法
通過 HandlerAdapter 可以使得 DispatchServlet 和具體的 Controller 解耦,擴充套件 Controller 時,我們只需要增加一個介面卡類就完成了 SpringMVC 的擴充套件
對於同一類的請求方式,我們封裝一個 HandlerAdapter 實現類,通過該 HandlerAdapter 實現類完成一類相同的請求

7、介面卡模式的注意事項

介面卡模式的注意事項和細節

三種命名方式,是根據src是以怎樣的形式給到Adapter(在Adapter裡的形式)來命名的。

類介面卡:以類給到,在Adapter裡,就是將src當做類,繼承
物件介面卡:以物件給到,在Adapter裡,將src作為一個物件,持有
介面介面卡:以介面給到,在Adapter裡,將src作為一個介面,實現
Adapter模式最大的作用還是將原本不相容的介面融合在一起工作,相當於是個中轉封裝站

實際開發中,實現起來不拘泥於我們講解的三種經典形式

✪介面卡模式
如果有兩個業務毫無相關的類需要關聯處理默寫問題的時候,這個時候如果直接在兩個類某一個類中直接呼叫對方來編寫是會增加類的耦合性,這個時候可以新建一個類作為適配類。然後通過聚合來實現功能

相關文章