前言
介面卡模式是最為普遍的設計模式之一,它不僅廣泛應用於程式碼開發,在日常生活裡也很常見。比如筆記本上的電源介面卡,可以使用在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的邏輯,符合開閉原則。
關於介面卡模式的介紹就到這裡了,有不足之處還望指出。
參考
《設計模式之禪》