設(shè)計模式系列 — 責任鏈模式

點贊再看,養(yǎng)成習(xí)慣淋硝,公眾號搜一搜【一角錢技術(shù)】關(guān)注更多原創(chuàng)技術(shù)文章雹熬。本文 GitHub org_hejianhui/JavaStudy 已收錄,有我的系列文章谣膳。

前言

23種設(shè)計模式快速記憶的請看上面第一篇竿报,本篇和大家一起來學(xué)習(xí)責任/職責鏈模式相關(guān)內(nèi)容。

模式定義

為了避免請求發(fā)送者與多個請求處理者耦合在一起继谚,將所有請求的處理者通過前一對象記住其下一個對象的引用而連成一條鏈烈菌;當有請求發(fā)生時,可將請求沿著這條鏈傳遞花履,直到有對象處理它為止芽世。

注意:責任鏈模式也叫職責鏈模式。

在責任鏈模式中诡壁,客戶只需要將請求發(fā)送到責任鏈上即可济瓢,無須關(guān)心請求的處理細節(jié)和請求的傳遞過程,所以責任鏈將請求的發(fā)送者和請求的處理者解耦了妹卿。

模版實現(xiàn)如下

package com.niuh.designpattern.chainofresponsibility.v1;

/**
 * 責任鏈/職責鏈模式
 */
public class ChainOfResponsibilityPattern {
    public static void main(String[] args) {
        //組裝責任鏈
        Handler handler1=new ConcreteHandler1();
        Handler handler2=new ConcreteHandler2();
        handler1.setNext(handler2);
        //提交請求
        handler1.handleRequest("two");
    }
}

//抽象處理者角色
abstract class Handler {
    private Handler next;

    public void setNext(Handler next) {
        this.next = next;
    }

    public Handler getNext() {
        return next;
    }

    //處理請求的方法
    public abstract void handleRequest(String request);
}


//具體處理者角色1
class ConcreteHandler1 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("one")) {
            System.out.println("具體處理者1負責處理該請求旺矾!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("沒有人處理該請求!");
            }
        }
    }
}

//具體處理者角色2
class ConcreteHandler2 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("two")) {
            System.out.println("具體處理者2負責處理該請求夺克!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("沒有人處理該請求箕宙!");
            }
        }
    }
}

輸出結(jié)果如下

具體處理者2負責處理該請求!

解決的問題

將請求和處理分開懊直,請求者不用知道是誰處理的,處理者不用知道請求的全貌火鼻,實現(xiàn)了兩者之間的解耦室囊。

模式組成

組成(角色) 作用
抽象處理者(Handler)角色 定義一個處理請求的接口,包含抽象處理方法和一個后繼連接
具體處理者(Concrete Handler)角色 實現(xiàn)抽象處理者的處理方法魁索,判斷能否處理本次請求融撞,如果可以處理請求則處理,否則將該請求轉(zhuǎn)給它的后繼者粗蔚。
客戶類(Client)角色 創(chuàng)建處理鏈尝偎,并向鏈頭的具體處理者對象提交請求,它不關(guān)心處理細節(jié)和請求的傳遞過程鹏控。

實例說明

實例概況

用責任鏈模式設(shè)計一個請假條審批模塊致扯。

分析:假如規(guī)定學(xué)生請假小于或等于 2 天,班主任可以批準当辐;小于或等于 7 天抖僵,系主任可以批準;小于或等于 10 天缘揪,院長可以批準耍群;其他情況不予批準义桂;這個實例適合使用職責鏈模式實現(xiàn)。

首先蹈垢,定義一個領(lǐng)導(dǎo)類(Leader)慷吊,它是抽象處理者,包含了一個指向下一位領(lǐng)導(dǎo)的指針 next 和一個處理假條的抽象處理方法 handleRequest(int LeaveDays)曹抬;然后溉瓶,定義班主任類(ClassAdviser)、系主任類(DepartmentHead)和院長類(Dean)沐祷,它們是抽象處理者的子類嚷闭,是具體處理者,必須根據(jù)自己的權(quán)力去實現(xiàn)父類的 handleRequest(int LeaveDays) 方法赖临,如果無權(quán)處理就將假條交給下一位具體處理者胞锰,直到最后;客戶類負責創(chuàng)建處理鏈兢榨,并將假條交給鏈頭的具體處理者(班主任)嗅榕。

使用步驟

步驟1:定義抽象處理者(Handler)角色,抽象處理者:領(lǐng)導(dǎo)類

abstract class Leader {
    private Leader next;

    public void setNext(Leader next) {
        this.next = next;
    }

    public Leader getNext() {
        return next;
    }

    //處理請求的方法
    public abstract void handleRequest(int LeaveDays);
}

步驟2:定義具體處理者(Concrete Handler)角色吵聪,具體處理者1:班主任類

class ClassAdviser extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 2) {
            System.out.println("班主任批準您請假" + LeaveDays + "天凌那。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("請假天數(shù)太多,沒有人批準該假條吟逝!");
            }
        }
    }
}

步驟3:定義具體處理者(Concrete Handler)角色帽蝶,具體處理者2:系主任類

class DepartmentHead extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 7) {
            System.out.println("系主任批準您請假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("請假天數(shù)太多块攒,沒有人批準該假條励稳!");
            }
        }
    }
}

步驟4:定義具體處理者(Concrete Handler)角色,具體處理者2:院長類

class Dean extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 10) {
            System.out.println("院長批準您請假" + LeaveDays + "天囱井。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("請假天數(shù)太多驹尼,沒有人批準該假條!");
            }
        }
    }
}

假如增加一個教務(wù)處長類庞呕,可以批準學(xué)生請假 20 天新翎,也非常簡單

class DeanOfStudies extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 20) {
            System.out.println("教務(wù)處長批準您請假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("請假天數(shù)太多住练,沒有人批準該假條地啰!");
            }
        }
    }
}

步驟5:測試

public class ChainOfResponsibilityPattern {

    public static void main(String[] args) {
        //組裝責任鏈 
        Leader teacher1 = new ClassAdviser();
        Leader teacher2 = new DepartmentHead();
        Leader teacher3 = new Dean();
        //Leader teacher4=new DeanOfStudies();
        teacher1.setNext(teacher2);
        teacher2.setNext(teacher3);
        //teacher3.setNext(teacher4);
        //提交請求 
        teacher1.handleRequest(8);
    }
}

輸出結(jié)果

院長批準您請假8天。

優(yōu)點

  1. 降低了對象之間的耦合度讲逛。該模式使得一個對象無須知道到底是哪一個對象處理其請求以及鏈的結(jié)構(gòu)髓绽,發(fā)送者和接收者也無須擁有對方的明確信息。
  2. 增強了系統(tǒng)的可擴展性妆绞∷撑唬可以根據(jù)需要增加新的請求處理類枫攀,滿足開閉原則。
  3. 增強了給對象指派職責的靈活性株茶。當工作流程發(fā)生變化来涨,可以動態(tài)地改變鏈內(nèi)的成員或者調(diào)動它們的次序,也可動態(tài)地新增或者刪除責任启盛。
  4. 責任鏈簡化了對象之間的連接蹦掐。每個對象只需保持一個指向其后繼者的引用,不需保持其他所有處理者的引用僵闯,這避免了使用眾多的 if 或者 if···else 語句卧抗。
  5. 責任分擔。每個類只需要處理自己該處理的工作鳖粟,不該處理的傳遞給下一個對象完成社裆,明確各類的責任范圍,符合類的單一職責原則向图。

缺點

  1. 不能保證每個請求一定被處理泳秀。由于一個請求沒有明確的接收者,所以不能保證它一定會被處理榄攀,該請求可能一直傳到鏈的末端都得不到處理嗜傅。
  2. 對比較長的職責鏈,請求的處理可能涉及多個處理對象檩赢,系統(tǒng)性能將受到一定影響吕嘀。
  3. 職責鏈建立的合理性要靠客戶端來保證,增加了客戶端的復(fù)雜性贞瞒,可能會由于職責鏈的錯誤設(shè)置而導(dǎo)致系統(tǒng)出錯偶房,如可能會造成循環(huán)調(diào)用。

應(yīng)用場景

  1. 有多個對象可以處理一個請求憔狞,哪個對象處理該請求由運行時刻自動確定蝴悉。
  2. 可動態(tài)指定一組對象處理請求彰阴,或添加新的處理者瘾敢。
  3. 在不明確指定請求處理者的情況下,向多個處理者中的一個提交請求尿这。

模式的擴展

職責鏈模式存在以下兩種情況簇抵。

  1. 純的職責鏈模式:一個請求必須被某一個處理者對象所接收,且一個具體處理者對某個請求的處理只能采用以下兩種行為之一:自己處理(承擔責任)射众;把責任推給下家處理碟摆。
  2. 不純的職責鏈模式:允許出現(xiàn)某一個具體處理者對象在承擔了請求的一部分責任后又將剩余的責任傳給下家的情況,且一個請求可以最終不被任何接收端對象所接收叨橱。

源碼中的應(yīng)用

Filter

web.xml 配置filter

    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>
            org.springframework.web.filter.CharacterEncodingFilter
        </filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <servlet-name>dispatcher</servlet-name>
    </filter-mapping>

filter有兩種過濾匹配方式典蜕,一種是url-pattern 一種是servlet-name 來匹配

Filter 概述

Servlet API中提供了一個Filter接口断盛,開發(fā)web應(yīng)用時,如果編寫的Java類實現(xiàn)了這個接口愉舔,則把這個java類稱之為過濾器Filter钢猛。

通過Filter技術(shù),開發(fā)人員可以實現(xiàn)用戶在訪問某個目標資源之前轩缤,對訪問的請求和響應(yīng)進行攔截命迈。簡單說,就是可以實現(xiàn)web容器對某資源的訪問前截獲進行相關(guān)的處理火的,還可以在某資源向web容器返回響應(yīng)前進行截獲進行處理壶愤。

Filter工作流程圖如下:[圖片上傳失敗...(image-354188-1603983809362)]
Tomcat中的Filter 是采用責任鏈設(shè)計模式 ,下面我們通過源碼分析下tomcat中Filter的實現(xiàn)

這里使用springboot 內(nèi)嵌tomcat以便于分析
肯定有人很好奇馏鹤,F(xiàn)ilter源碼入口是怎么找到的征椒。

我們可以借助idea的debug工具,查看調(diào)用方法的調(diào)用棧假瞬,這樣就可以知道想看的源碼是哪里開始的了

Filter源碼解析

入口為StandardWrapperValve.invoke()方法

StandardWrapperValve.invoke()方法

使用ApplictionFilterFactory創(chuàng)建FilterChain

ApplicationFilterChain

一個請求到達陕靠,生成ApplicationFilterChain對象,把servlet對象設(shè)置進去之后脱茉,添加匹配的filter(先根據(jù)url找剪芥,再根據(jù)name找,總共遍歷兩遍琴许,想不懂為啥要兩遍税肪,一次遍歷,不好嗎榜田?)


  • 把要執(zhí)行的servlet存放到過濾器鏈中益兄。

  • 如果沒有配置過濾器則return一個空的過濾器鏈(只包含上面設(shè)置的servlet)。

  • 如果配置過濾器箭券,則把所有配置的過濾器加入到過濾器鏈中

    1. 首先判斷filter-mapping中配置的url-pattern規(guī)則净捅,如果符合則添加到過濾器鏈

    2. 然后判斷filter-mapping中配置的servlet-name規(guī)則,如果符合則添加到過濾器鏈

過濾器的順序是按照web.xml中filtermapping 的url-pattern匹配順序+ servlet-name匹配順序

繼續(xù)往下執(zhí)行StandardWrapperValve.invoke()方法辩块,往下執(zhí)行會執(zhí)行到filterChain.doFilter()

doFilter方法

可以看到執(zhí)行internalDoFilter()方法

調(diào)用internalDoFilter方法

ApplicationFilterChain#internalDoFilter()



pos:為過濾器鏈中當前執(zhí)行的過濾器下標蛔六。
n:過濾器鏈中的過濾器個數(shù)。
每執(zhí)行一個過濾器則把過濾器鏈中的post+1(下標)废亭,直到所有的過濾器的doFilter方法都調(diào)用成功国章。

filter.doFilter(request, response, this);

這行代碼是責任鏈設(shè)計模式的核心,把當前的過濾鏈傳入到doFilter方法中豆村。這樣在Filter中進行攔截液兽,通過過濾器判斷是否要進行調(diào)用下一個過濾器鏈。
如下:
如果不執(zhí)行FilterChain#doFilter()方法掌动,責任鏈就會終止

過濾器鏈中的所有過濾器的doFilter方法都執(zhí)行完成后四啰,最后再調(diào)用過濾器鏈中存放的servlet.service()方法宁玫。


PS:以上代碼提交在 Githubhttps://github.com/Niuh-Study/niuh-designpatterns.git

文章持續(xù)更新,可以公眾號搜一搜「 一角錢技術(shù) 」第一時間閱讀柑晒, 本文 GitHub org_hejianhui/JavaStudy 已經(jīng)收錄撬统,歡迎 Star。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末敦迄,一起剝皮案震驚了整個濱河市恋追,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌罚屋,老刑警劉巖苦囱,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異脾猛,居然都是意外死亡撕彤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門猛拴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來羹铅,“玉大人,你說我怎么就攤上這事愉昆≈霸保” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵跛溉,是天一觀的道長焊切。 經(jīng)常有香客問我,道長芳室,這世上最難降的妖魔是什么专肪? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮堪侯,結(jié)果婚禮上嚎尤,老公的妹妹穿的比我還像新娘。我一直安慰自己伍宦,他們只是感情好芽死,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著雹拄,像睡著了一般收奔。 火紅的嫁衣襯著肌膚如雪掌呜。 梳的紋絲不亂的頭發(fā)上滓玖,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機與錄音质蕉,去河邊找鬼势篡。 笑死翩肌,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的禁悠。 我是一名探鬼主播念祭,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼碍侦!你這毒婦竟也來了粱坤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤瓷产,失蹤者是張志新(化名)和其女友劉穎站玄,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體濒旦,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡株旷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了尔邓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晾剖。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖梯嗽,靈堂內(nèi)的尸體忽然破棺而出齿尽,到底是詐尸還是另有隱情,我是刑警寧澤灯节,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布雕什,位于F島的核電站,受9級特大地震影響显晶,放射性物質(zhì)發(fā)生泄漏贷岸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一磷雇、第九天 我趴在偏房一處隱蔽的房頂上張望偿警。 院中可真熱鬧,春花似錦唯笙、人聲如沸螟蒸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽七嫌。三九已至,卻和暖如春苞慢,著一層夾襖步出監(jiān)牢的瞬間诵原,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绍赛,地道東北人蔓纠。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像吗蚌,于是被迫代替她去往敵國和親腿倚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361