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

来源:https://www.cnblogs.com/yeya/archive/2019/08/07/11269793.html
-Advertisement-
Play Games

[toc] 前言 適配器模式是最為普遍的設計模式之一,它不僅廣泛應用於代碼開發,在日常生活里也很常見。比如筆記本上的電源適配器,可以使用在110~ 220V之間變化的電源,而筆記本還能正常工作,這就是適配器模式最直接的例子,同時也是其思想的體現,簡單的說,適配器模式就是把一個類(介面)轉換成其他的類 ...


目錄

前言

適配器模式是最為普遍的設計模式之一,它不僅廣泛應用於代碼開發,在日常生活里也很常見。比如筆記本上的電源適配器,可以使用在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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近在研究MUI框架時,看到這樣的代碼: init,這個外面還有一層: 有些明瞭了,這就像java中對象的裡面定義了一個init方法,這裡init方法也是初始化方法,這裡是對整個頁面進行初始化。調用類似java:_App.init()即可. 關於細節說法:這是一種json寫法,把所有的functio ...
  • 下載NEditor 放在 vue 項目下麵 public 文件中。 安裝 vue-neditor-wrap 執行命令 npm install vue-neditor-wrap 代碼使用 <VueNeditorWrap ref="VueNeditorWrap" v-model="content" :c ...
  • 摘要: 理解函數式編程。 作者:前端小智 原文: "JS中函數式編程基本原理簡介" "Fundebug" 經授權轉載,版權歸原作者所有。 在長時間學習和使用面向對象編程之後,咱們退一步來考慮系統複雜性。 在做了一些研究之後,我發現了函數式編程的概念,比如不變性和純函數。這些概念使你能夠構建無副作用的 ...
  • jTopo 幫助說明網站 http://www.jtopo.com/index.html 使用例子: http://www.jtopo.com/demo/helloworld.html 不建議直接安裝 github 上的代碼,因為代碼版本不是最新,有部分功能未實現。 下載最新的js類庫文件放到 vu ...
  • vis.js 網站 https://visjs.org/ vs code 下安裝命令 npm install vis-network 在vue 下引入 vis-network組件 const vis = require("vis-network/dist/vis-network.min.js"); ...
  • ## DOM簡單學習:為了滿足案例要求 * 功能:控制html文檔的內容 * 獲取頁面標簽(元素)對象:Element * document.getElementById("id值"):通過元素的id獲取元素對象 * 操作Element對象: 1. 修改屬性值: 1. 明確獲取的對象是哪一個? 2.... ...
  • 單例模式介紹 單例模式,是為了確保在整個軟體體統中,某個類對象只有一個實例,並且該類通常會提供一個對外獲取該實例的public方法(靜態方法)。 比如日誌、資料庫連接池等對象,通常需要且只需要一個實例對象,這就會使用單例模式。 單例模式的八種模式 餓漢式(靜態常量) 餓漢式(靜態代碼塊) 懶漢式(線 ...
  • 設計模式介紹 設計模式是程式員在面對同類軟體工程設計問題所總結出來的有用的經驗,模式不是代碼,而是某類問題的通用解決方案, 設計模(Design pattern)代表了最佳的實踐。這些解決方案是眾多軟體開發人員經過相當長的一段時間的試驗和錯誤總結出來的。 設計模式的本質提高 軟體的維護性,通用性和擴 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...