首頁>技術>

前言

文章開篇,丟擲一個老生常談的問題,學習設計模式有什麼作用?

設計模式主要是為了應對程式碼的複雜性,讓其滿足開閉原則,提高程式碼的擴充套件性

另外,學習的設計模式 一定要在業務程式碼中落實,只有理論沒有真正實施,是無法真正掌握並且靈活運用設計模式的

這篇文章主要說 責任鏈設計模式,認識此模式是在讀 Mybatis 原始碼時, Interceptor 攔截器主要使用的就是責任鏈,當時讀過後就留下了很深的印象(內心 OS:還能這樣玩)

文章先從基礎概念說起,另外分析一波 Mybatis 原始碼中是如何運用的,最後按照 "習俗",設計一個真實業務場景上的應用

責任鏈設計模式大綱如下:

什麼是責任鏈模式 完成真實的責任鏈業務場景設計 Mybatis Interceptor 底層實現 責任鏈模式總結

什麼是責任鏈模式

舉個例子,SpringMvc 中可以定義攔截器,並且可以定義多個。當一個使用者發起請求時,順利的話請求會經過所有攔截器,最終到達業務程式碼邏輯,SpringMvc 攔截器設計就是使用了責任鏈模式

為什麼說順利的話會經過所有攔截器?因為請求不滿足攔截器自定義規則會被打回,但這並不是責任鏈模式的唯一處理方式,繼續往下看

在責任鏈模式中,多個處理器(參照上述攔截器)依次處理同一個請求。一個請求先經過 A 處理器處理,然後再把請求傳遞給 B 處理器,B 處理器處理完後再傳遞給 C 處理器,以此類推,形成一個鏈條,鏈條上的每個處理器 各自承擔各自的處理職責

責任鏈模式中多個處理器形成的處理器鏈在進行處理請求時,有兩種處理方式:

請求會被 所有的處理器都處理一遍,不存在中途終止的情況,這裡參照 MyBatis 攔截器理解二則是處理器鏈執行請求中,某一處理器執行時,如果不符合自制定規則的話,停止流程,並且剩下未執行處理器就不會被執行,大家參照 SpringMvc 攔截器理解

這裡透過程式碼的形式對兩種處理方式作出解答,方便讀者更好的理解。首先看下第一種,請求會經過所有處理器執行的情況

IHandler 負責抽象處理器行為,handle() 則是不同處理器具體需要執行的方法,HandleA、HandleB 為具體需要執行的處理器類,HandlerChain 則是將處理器串成一條鏈執行的處理器鏈

public class ChainApplication {    public static void main(String[] args) {        HandlerChain handlerChain = new HandlerChain();        handlerChain.addHandler(Lists.newArrayList(new HandlerA(), new HandlerB()));        handlerChain.handle();        /**         * 程式執行結果:         * HandlerA列印:執行 HandlerA         * HandlerB列印:執行 HandlerB         */    }}

這種責任鏈執行方式會將所有的 處理器全部執行一遍,不會被打斷。Mybatis 攔截器用的正是此型別,這種型別 重點在對請求過程中的資料或者行為進行改變

而另外一種責任鏈模式實現,則是會對請求有阻斷作用,阻斷產生的前置條件是在處理器中自定義的,程式碼中的實現較簡單,讀者可以聯想 SpringMvc 攔截器的實現流程

根據程式碼看得出來,在每一個 IHandler 實現類中會返回一個布林型別的返回值,如果返回布林值為 false,那麼責任鏈發起類會中斷流程,剩餘處理器將不會被執行。就像我們定義在 SpringMvc 中的 Token 攔截器,如果 Token 失效就不能繼續訪問系統,處理器將請求打回

public class ChainApplication {    public static void main(String[] args) {        HandlerChain handlerChain = new HandlerChain();        handlerChain.addHandler(Lists.newArrayList(new HandlerA(), new HandlerB()));        boolean resultFlag = handlerChain.handle();        if (!resultFlag) {            System.out.println("責任鏈中處理器不滿足條件");        }    }}

讀者可以自己在 IDEA 中實現兩種不同的責任鏈模式,對比其中的不同,設想下業務中真實的應用場景,再或者可以跑 SpringBoot 專案,建立多個攔截器來佐證文中的說辭

本章節介紹了責任鏈設計模式的具體語義,以及不同責任鏈實現型別程式碼舉例,並以 Mybatis、SpringMvc 攔截器為參照點,介紹各自不同的程式碼實現以及應用場景

責任鏈業務場景設計

趁熱打鐵,本小節對使用的真實業務場景進行舉例說明。假設業務場景是這樣的,我們 系統處在一個下游服務,因為業務需求,系統中所使用的 基礎資料需要從上游中臺同步到系統資料庫

基礎資料包含了很多型別資料,雖然資料在中臺會有一定驗證,但是 資料只要是人為錄入就極可能存在問題,遵從對上游系統不信任原則,需要對資料接收時進行一系列校驗

最初是要進行一系列驗證原則才能入庫的,後來因為工期問題只放了一套非空驗證,趁著春節期間時間還算寬裕,把這套驗證規則骨架放進去

從我們系統的接入資料規則而言,個人覺得需要支援以下幾套規則

必填項校驗,如果資料無法滿足業務所必須欄位要求,資料一旦落入庫中就會產生一系列問題非法字元校驗,因為資料如何錄入,上游系統的錄入規則是什麼樣的我們都不清楚,這一項規則也是必須的長度校驗,理由同上,如果系統某欄位長度限制 50,但是接入來的資料 500長度,這也會造成問題

為了讓讀者瞭解業務嵌入責任鏈模式的前因,這裡列舉了三套校驗規則,當然真實中可能不止這三套。但是 一旦將責任鏈模式嵌入資料同步流程,就會 完全符合文初所提的開閉原則,提高程式碼的擴充套件性

本案例設計模式中的開閉原則透過 Spring 提供支援,後續新增新的校驗規則就可以不必修改原有程式碼

這裡要再強調下,設計模式的應用場景一定要靈活掌握,只有這樣才能在合適的業務場景合理運用物件的設計模式

既然設計模式場景說過了,最後說一下需要達成的業務需求。將一個批次資料經過處理器鏈的處理,返回出符合要求的資料分類

定義頂級驗證介面和一系列處理器實現類沒什麼難度,但是應該如何進行鏈式呼叫呢?

這一塊程式碼需要有一定 Spring 基礎才能理解,一起來看下 VerifyHandlerChain 如何將所有處理器串成一條鏈

VerifyHandlerChain 處理流程如下:

實現自 InitializingBean 介面,在對應實現方法中獲取 IOC 容器中型別為 VerifyHandler 的 Bean,也就是 EmptyVerifyHandler、SexyVerifyHandler將 VerifyHandler 型別的 Bean 新增到處理器鏈容器中定義校驗方法 verify(),對入引數據展開處理器鏈的全部呼叫,如果過程中發現已無需要驗證的資料,直接返回

這裡使用 SpringBoot 專案中預設測試類,來測試一下如何呼叫

@SpringBootTestclass ChainApplicationTests {    @Autowired    private VerifyHandlerChain verifyHandlerChain;    @Test    void contextLoads() {        List<Object> verify = verifyHandlerChain.verify(Lists.newArrayList("原始碼興趣圈", "@龍臺"));        System.out.println(verify);    }}

這樣的話,如果客戶或者產品提校驗相關的需求時,我們只需要實現 VerifyHandler 介面新建個校驗規則實現類就 OK 了,這樣符合了設計模式的原則:滿足開閉原則,提高程式碼的擴充套件性

熟悉之前作者寫過設計模式的文章應該知道,強調設計模式重語意,而不是具體的實現過程。所以,你看這個咱們這個校驗程式碼,把責任鏈兩種模式結合了使用

上面的程式碼只是示例程式碼,實際業務中的實現要比這複雜很多,比如:

如何定義處理器的先後呼叫順序。比如說某一個處理器執行時間很長並且過濾資料很少,所以希望把它放到最後面執行這是為當前業務的所有資料型別進行過濾,如何自定義單個數據型別過濾。比如你接入學生資料,學號有一定校驗規則,這種處理器類肯定只適合單一型別

還有很多的業務場景,所以設計模式強調的應該是一種思想,而不是固定的程式碼寫法,需要結合業務場景靈活變通

責任鏈模式的好處

一定要使用責任鏈模式麼?不使用能不能完成業務需求?

回答是肯定可以,設計模式只是幫助減少程式碼的複雜性,讓其滿足開閉原則,提高程式碼的擴充套件性。如果不使用同樣可以完成需求

如果不使用責任鏈模式,上面說的真實同步場景面臨兩個問題

如果把上述說的程式碼邏輯校驗規則寫到一起,毫無疑問這個類或者說這個方法函式奇大無比。減少程式碼複雜性一貫方法是:將大塊程式碼邏輯拆分成函式,將大類拆分成小類,是應對程式碼複雜性的常用方法。如果此時說:可以把不同的校驗規則拆分成不同的函式,不同的類,這樣不也可以滿足減少程式碼複雜性的要求麼。這樣拆分是能解決程式碼複雜性,但是這樣就會面臨第二個問題開閉原則:新增一個新的功能應該是,在已有程式碼基礎上擴充套件程式碼,而非修改已有程式碼。大家設想一下,假設你寫了三套校驗規則,執行過一段時間,這時候領導讓加第四套,是不是要在原有程式碼上改動

綜上所述,在合適的場景運用適合的設計模式,能夠讓程式碼設計複雜性降低,變得更為健壯。朝更遠的說也能讓自己的編碼設計能力有所提高,告別被人吐槽的爛程式碼...

Mybatis Interceptor底層實現

上面說了那麼多,框架底層原始碼是怎麼設計並且使用責任鏈模式的?之前在看 Mybatis 3.4.x 原始碼時瞭解到 Interceptor 底層實現就是責任鏈模式,這裡和讀者分享 Interceptor 具體實現

開門見山,直接把視線聚焦到 Mybatis 原始碼,版本號 3.4.7-SNAPSHOT

熟悉麼?是不是和我們上面用到的責任鏈模式差不太多,有處理器集合 interceptors,有新增處理器方法

Mybatis Interceptor 不僅用到了責任鏈,還用到了動態代理,服務於 Mybatis 四大 "護教法王",在建立物件時透過動態代理和責任鏈相結合組裝而成外掛模組

ParameterHandlerResultSetHandlerStatementHandlerExecutor

使用過 Mybatis 的讀者應該知道,查詢 SQL 的分頁語句就是使用 Interceptor 實現,比如市場上的 PageHelper、Mybatis-Plus 分頁外掛再或者我們自實現的分頁外掛(應該沒有專案組使用顯示呼叫多條語句組成分頁吧)

拿查詢語句舉例,如果定義了多個查詢相關的攔截器,會先經過攔截器的程式碼加工,所有的攔截器執行完畢後才會走真正查詢資料庫操作

扯的話就扯遠了,能夠知道如何用、在哪用就可以了。透過 Interceptor 也能知道一點,想要讀框架原始碼,需要一定的設計模式基礎。如果對責任鏈、動態代理不清楚,那麼就不能理解這一塊的精髓

結言

文章透過圖文並茂的方式幫助大家理解責任鏈設計模式,在兩種型別示例程式碼以及舉例實際業務場景下,相信小夥伴已經掌握瞭如何在合適的場景使用責任鏈設計模式

看完文章後可以結合 Mybatis、SpringMvc 攔截器更深入掌握責任鏈模式的應用場景以及使用手法。另外可以結合專案中實際業務場景靈活使用,相信真正使用後的你會對責任鏈模式產生更深入的瞭解

參考文章

11
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 資料科學專業人員必須知道的8大深度學習概念