前言
只有光頭才能變強。
文字已收錄至我的GitHub精選文章,歡迎Star:github.com/ZhongFuChen…
最近在看專案程式碼的時候發現「責任鏈模式」,於是想花點時間來寫寫什麼是責任鏈模式。
不知道大家是怎麼學習設計模式的,一般我都是用到的時候,或者接觸到的時候才會去學。否則感覺學完就很容易就忘掉了,不能理解為什麼要使用設計模式(因為沒有真實的場景給我去使用)。
在之前我已經更新說幾篇設計模式的文章了,我覺得寫得「還行」,有興趣的同學可以到我的GitHub上,關鍵字搜尋「設計模式」,就能找到對應的文章。
不得不說,我現在負責專案的程式碼我常常會感嘆:**這程式碼怎麼這麼騷啊!**專案裡邊用到了很多的設計模式,在最開始看的時候會很費勁(因為之前沒學),但維護起來是真的方便。
一、什麼是責任鏈模式?
在說責任鏈模式之前,我們先來聊聊「過濾器」。
過濾器相信大家都肯定學過了,在最開始學Servlet的時候我們會學到Filter
。等學到Struts2的時候,我們會學到Interceptor
。等學到SpringMVC的時候,我們會學到HandlerInterceptor
。
但無論學哪個框架,我們發現是最終其實它還是做Filter
這麼一件事。說白了就是:
- 把所有的過濾器都放在FilterChain裡邊,依次執行每個過濾器。
在我的GitHub對Filter,HandlerInterceptor,Interceptor都有講到,如果想要複習的同學不妨進去搜尋關鍵字「過濾器」「Struts2」「SpringMVC」
為什麼看責任鏈模式要聊「過濾器」呢?後面會講到,不要著急。
1.1 幹掉敖丙和雞蛋
舉個例子:把我們的正常請求想象成一堆的雜物,裡邊有米豆,有雞蛋,有敖丙公仔玩具等等一些雜物。
現在我們想要最後得到的是米豆,雞蛋和敖丙玩具都被過濾掉。於是我們就可以搞兩個濾網,把敖丙玩具和雞蛋給過濾掉。
以最快的方式,我們可以寫if
來把這個需求給搞掂,下面上程式碼。
一個請求,我們使用Request
物件來表示:
public class Request {
// 請求的資料
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
複製程式碼
針對請求,我們肯定是有一個介面處理請求的啦,我們使用Handler
來表示:
public class Handler {
public void handlerRequest(Request request) {
// 得到請求的資料
String data = request.getData();
if (data.contains("雞蛋")) {
filterEgg(data);
}
if (data.contains("敖丙工具")) {
filterAoBing(data);
}
// 我到這裡就能拿到米豆了。
}
private void filterAoBing(String data) {
//doSomething
}
private void filterEgg(String data) {
//doSomething
}
}
複製程式碼
上面的程式碼大家不知道熟不熟悉,反正我就很熟悉,很多時候我就是這樣寫程式碼的(在現實裡邊很多程式碼就是這樣的)。
1.2 如何更加優雅幹掉敖丙和雞蛋?
在某年某月產品過來告訴我,需要新增一種型別想要過濾的「白菜」
在某年某月產品過來告訴我,需要新增一種型別想要過濾的「雞腿」
在某年某月產品過來告訴我,需要新增一種型別想要過濾的「雞頭」
於是我們的Handler處理就可能「膨脹」起來了,可能是這樣?
public class Handler {
public void handlerRequest(Request request) {
// 得到請求的資料
String data = request.getData();
if (data.contains("雞蛋")) {
filterEgg(data);
}
if (data.contains("敖丙工具")) {
filterAoBing(data);
}
if (data.contains("白菜")) {
filterBaiCai(data);
}
if (data.contains("雞頭")) {
filterJiTou(data);
}
if (data.contains("雞腿")) {
filterJiTui(data);
}
// 我到這裡就能拿到米豆了。
}
private void filterJiTou(String data) {
//doSomething
}
private void filterJiTui(String data) {
//doSomething
}
private void filterAoBing(String data) {
//doSomething
}
private void filterEgg(String data) {
//doSomething
}
}
複製程式碼
明顯的是,如果處理的流程改動比較大的話(需要增刪改其中的某個流程),那我每次都需要更改handlerRequest
的程式碼,增加/修改/刪除一個if
和一個處理方法。
更加物件導向的方式是這樣的:將每個處理的方式抽象成一個類,每個類各司其職。
無論是過濾敖丙還是過濾雞蛋還是過濾米豆,做的事都是過濾。我們就可以將其抽象成介面。於是我們就有一個介面,多個實現類。
public interface Filter {
// 過濾
void doFilter(String data);
}
class FilterEgg implements Filter {
@Override
public void doFilter(String data) {
//doSomething
}
}
class FilterAoBing implements Filter {
@Override
public void doFilter(String data) {
//doSomething
}
}
class FilterBaiCai implements Filter {
@Override
public void doFilter(String data) {
//doSomething
}
}
class FilterJiTou implements Filter {
@Override
public void doFilter(String data) {
//doSomething
}
}
複製程式碼
每個各司其職的Filter都有可能被執行,我們可以將其串成一條鏈,抽象一層對外只暴露一個方法來替代if
。於是我們可以寫出一個FilterChain
類
public class FilterChain {
List<Filter> filters = new ArrayList<>();
public FilterChain() {
filters.add(new FilterEgg());
filters.add(new FilterAoBing());
filters.add(new FilterBaiCai());
filters.add(new FilterJiTou());
}
public void processData(String data) {
for (Filter filter : filters) {
filter.doFilter(data);
}
}
}
複製程式碼
改造過後,我們的Handler
就長這個樣子了:
public class Handler {
public void handlerRequest(Request request) {
// 得到請求的資料
String data = request.getData();
FilterChain filterChain = new FilterChain();
// 處理資料
filterChain.processData(data);
}
}
複製程式碼
如果我告訴你,這種的處理方式就是責任鏈模式,你會怎麼想?
二、為什麼責任鏈模式?
再來回顧一下,我做了些什麼:
- 將處理的各個流程抽象為各個類(本來Handler裡邊有多個
if
和方法
) - 將多個類用
Chain
鏈起來,暴露一個方法給Handler使用 - done
下面我畫了一張對比圖:
是不是很簡單?說到底還是抽象了一層(將每個處理抽象為一個類而已)。
那為什麼要這樣幹?如果我要增加一個處理流程,我是得新增一個處理類,然後在鏈上增加相對應的類。操作也的確如此。
這不麻煩嗎?要便捷的話,我還不如直接增加一個if
,一個處理方法來得方便呢。
用責任鏈模式的好處就是分工明確,解耦,容易維護。
- 將多個條件判定分散到各個的處理類上,相對於
if else
耦合性相對較低。 - 增加一個具體的
Handler
處理類,不會影響到BaseHandler
的程式碼
責任鏈模式的缺點:
- 專案裡邊會有多個具體Handler類(因為每種處理都抽象為一個類,所以會有多個類)
- 不好除錯,初看程式碼時不好閱讀。(對外只是一個
doChain
方法,而裡邊由多個處理類來組成,還得看相應的呼叫順序)
三、再來聊聊責任鏈模式
我們從上面也可以看到責任鏈模式主要有以下特點:
- 一個Handler介面,多個Handler處理類
- 多個Handler處理類串起來形成一條鏈
有這兩個特點我就稱這些程式碼運用了責任鏈模式。在翻閱資料或者看書的時候,你可能會看到:“純責任鏈和不純責任鏈”
-
純:請求執行到某個具體的Handler,該Handler要麼自行處理然後結束請求,要麼不處理繼續往下給別的Handler執行。
-
不純:請求執行到某個具體的Handler,該Handler自行處理了,還繼續往下給別的Handler執行。
還有就是將各個具體的Handler串成一條鏈,這裡邊的實現會有各式各樣的:
- 在我例子裡是直接
new
出一個ArrayList,然後在構造方法裡邊程式碼手動add到ArrayList的 - 有可能會在程式碼裡邊每個具體Handler都會記錄自己下一個Handler是誰
- 有可能將Handler的初始化放在XML上
- ....//反正各種操作最終還是會將各個Handler串起來。
其實不必要在意純和不純的責任鏈模式,我們學設計模式是為了學它的思想。
四、看看JavaWeb的Filter
在文章最開頭我就說了我們以前學過的Filter,其實Filter就是用了責任鏈模式。我們來簡單看看程式碼:
我們在使用Filter過濾器的時候,要麼在XML上配置<filter>
,要麼在程式碼上寫上註解@WebFilter(filterName = "",urlPatterns = "")
這些配置都會在Web容器啟動的時候被讀取,讀完這些配置,會將你寫的Filter過濾器加到FilterChain裡邊:
我們可以看到Filter介面下有很多都實現了doFilter
:
JavaWeb的Filter實際用到的也是責任鏈模式。
最後
設計模式本身不是一件很複雜的東西,像門面模式,模板方法模式都非常容易理解。學完了會有一種感覺:“啊?就這?”
重要的是學完能不能用到實際的工作中,這是非常難能可貴的。我們寫程式碼按照自身的思維寫if else
是非常簡單的,而設計模式往往需要繞一個圈才能把功能實現。
但是,合理運用設計模式的程式碼是非常好維護的。如果你懂設計模式,那程式碼會看起來非常清晰。如果你不懂設計模式,你就會感嘆“這程式碼是真的騷阿”(這就是我...)。
好好學習,希望有朝一日,別人看到我的程式碼,在背後說「這人寫的程式碼是真的騷,牛逼阿」。
參考資料:
- 《設計模式之禪》
- www.cnblogs.com/tanshaoshen…
本已收錄至我的GitHub精選文章,歡迎Star:github.com/ZhongFuChen…
樂於輸出乾貨的Java技術公眾號:Java3y。公眾號內有300多篇原創技術文章、海量視訊資源、精美腦圖,關注即可獲取!
非常感謝人才們能看到這裡,如果這個文章寫得還不錯,覺得「三歪」我有點東西的話 求點贊 求關注️ 求分享? 求留言? 對暖男我來說真的 非常有用!!!
創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見!