Java責(zé)任鏈模式

概述

責(zé)任鏈模式是一種對(duì)象的行為模式空骚。在責(zé)任鏈模式里偷遗,很多對(duì)象由每一個(gè)對(duì)象對(duì)其下家的引用而連接起來形成一條鏈粱年。請(qǐng)求在這個(gè)鏈上傳遞圈盔,直到鏈上的某一個(gè)對(duì)象決定處理此請(qǐng)求豹芯。發(fā)出這個(gè)請(qǐng)求的客戶端并不知道鏈上的哪一個(gè)對(duì)象最終處理這個(gè)請(qǐng)求,這使得系統(tǒng)可以在不影響客戶端的情況下動(dòng)態(tài)地重新組織和分配責(zé)任驱敲。

從擊鼓傳花談起

擊鼓傳花是一種熱鬧而又緊張的飲酒游戲铁蹈。在酒宴上賓客依次坐定位置,由一人擊鼓众眨,擊鼓的地方與傳花的地方是分開的木缝,以示公正。開始擊鼓時(shí)围辙,花束就開始依次傳遞我碟,鼓聲一落,如果花束在某人手中姚建,則該人就得飲酒矫俺。
比如說,賈母掸冤、賈赦厘托、賈政、賈寶玉和賈環(huán)是五個(gè)參加擊鼓傳花游戲的傳花者稿湿,他們組成一個(gè)環(huán)鏈铅匹。擊鼓者將花傳給賈母,開始傳花游戲饺藤“撸花由賈母?jìng)鹘o賈赦,由賈赦傳給賈政涕俗,由賈政傳給賈寶玉罗丰,又賈寶玉傳給賈環(huán),由賈環(huán)傳回給賈母再姑,如此往復(fù)萌抵,如下圖所示。當(dāng)鼓聲停止時(shí)元镀,手中有花的人就得執(zhí)行酒令绍填。



擊鼓傳花便是責(zé)任鏈模式的應(yīng)用。責(zé)任鏈可能是一條直線栖疑、一個(gè)環(huán)鏈或者一個(gè)樹結(jié)構(gòu)的一部分讨永。

責(zé)任鏈模式的結(jié)構(gòu)

下面使用了一個(gè)責(zé)任鏈模式的最簡(jiǎn)單的實(shí)現(xiàn)。


責(zé)任鏈模式涉及到的角色如下所示:
  ●  抽象處理者(Handler)角色:定義出一個(gè)處理請(qǐng)求的接口蔽挠。如果需要住闯,接口可以定義 出一個(gè)方法以設(shè)定和返回對(duì)下家的引用瓜浸。這個(gè)角色通常由一個(gè)Java抽象類或者Java接口實(shí)現(xiàn)。上圖中Handler類的聚合關(guān)系給出了具體子類對(duì)下家的引用比原,抽象方法handleRequest()規(guī)范了子類處理請(qǐng)求的操作插佛。
  ●  具體處理者(ConcreteHandler)角色:具體處理者接到請(qǐng)求后,可以選擇將請(qǐng)求處理掉量窘,或者將請(qǐng)求傳給下家雇寇。由于具體處理者持有對(duì)下家的引用,因此蚌铜,如果需要锨侯,具體處理者可以訪問下家。

源代碼

/**
 * 抽象處理者(Handler)角色
 */
public abstract class Handler {

    /**
     * 持有后繼的責(zé)任對(duì)象
     */
    private Handler successor;

    public Handler getSuccessor() {
        return successor;
    }

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    /**
     * 示意處理請(qǐng)求的方法冬殃,雖然這個(gè)示意方法是沒有傳入?yún)?shù)的
     * 但實(shí)際是可以傳入?yún)?shù)的囚痴,根據(jù)具體需要來選擇是否傳遞參數(shù)
     */
    public abstract void handleRequest();
}

/**
 * 具體的處理者
 */
public class ConcreteHandler1 extends Handler {

    /**
     * 處理方法,調(diào)用此方法處理請(qǐng)求
     */
    @Override
    public void handleRequest() {
        /**
         * 判斷是否有后繼的責(zé)任對(duì)象
         * 如果有审葬,就轉(zhuǎn)發(fā)請(qǐng)求給后繼的責(zé)任對(duì)象
         * 如果沒有深滚,則處理請(qǐng)求
         */
        if(getSuccessor() != null){
            System.out.println("Handler1放過請(qǐng)求");
            getSuccessor().handleRequest();
        }else{
            System.out.println("Handler1處理請(qǐng)求");
        }
    }

}

/**
 * 具體的處理者
 */
public class ConcreteHandler2 extends Handler {

    /**
     * 處理方法,調(diào)用此方法處理請(qǐng)求
     */
    @Override
    public void handleRequest() {
        /**
         * 判斷是否有后繼的責(zé)任對(duì)象
         * 如果有涣觉,就轉(zhuǎn)發(fā)請(qǐng)求給后繼的責(zé)任對(duì)象
         * 如果沒有痴荐,則處理請(qǐng)求
         */
        if(getSuccessor() != null){
            System.out.println("Handler2放過請(qǐng)求");
            getSuccessor().handleRequest();
        }else{
            System.out.println("Handler2處理請(qǐng)求");
        }
    }

}

/**
 * 客戶端
 */
public class Client {

    public static void main(String[] args) {

        //創(chuàng)建具體的處理者對(duì)象
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();

        //將handler2指定為1的下一級(jí)
        handler1.setSuccessor(handler2);

        handler1.handleRequest();
    }

}

可以看出,客戶端創(chuàng)建了兩個(gè)處理者對(duì)象官册,并指定第一個(gè)處理者對(duì)象的下家是第二個(gè)處理者對(duì)象生兆,而第二個(gè)處理者對(duì)象沒有下家。然后客戶端將請(qǐng)求傳遞給第一個(gè)處理者對(duì)象膝宁。

由于本示例的傳遞邏輯非常簡(jiǎn)單:只要有下家鸦难,就傳給下家處理;如果沒有下家昆汹,就自行處理明刷。因此,第一個(gè)處理者對(duì)象接到請(qǐng)求后满粗,會(huì)將請(qǐng)求傳遞給第二個(gè)處理者對(duì)象。由于第二個(gè)處理者對(duì)象沒有下家愚争,于是自行處理請(qǐng)求映皆。活動(dòng)時(shí)序圖如下所示轰枝。


使用場(chǎng)景

來考慮這樣一個(gè)功能:申請(qǐng)聚餐費(fèi)用的管理捅彻。
很多公司都是這樣的福利,就是項(xiàng)目組或者是部門可以向公司申請(qǐng)一些聚餐費(fèi)用鞍陨,用于組織項(xiàng)目組成員或者是部門成員進(jìn)行聚餐活動(dòng)步淹。

申請(qǐng)聚餐費(fèi)用的大致流程一般是:由申請(qǐng)人先填寫申請(qǐng)單从隆,然后交給領(lǐng)導(dǎo)審批,如果申請(qǐng)批準(zhǔn)下來缭裆,領(lǐng)導(dǎo)會(huì)通知申請(qǐng)人審批通過键闺,然后申請(qǐng)人去財(cái)務(wù)領(lǐng)取費(fèi)用,如果沒有批準(zhǔn)下來澈驼,領(lǐng)導(dǎo)會(huì)通知申請(qǐng)人審批未通過辛燥,此事也就此作罷。
不同級(jí)別的領(lǐng)導(dǎo)缝其,對(duì)于審批的額度是不一樣的挎塌,比如,項(xiàng)目經(jīng)理只能審批500元以內(nèi)的申請(qǐng)内边;部門經(jīng)理能審批1000元以內(nèi)的申請(qǐng)榴都;而總經(jīng)理可以審核任意額度的申請(qǐng)。

也就是說漠其,當(dāng)某人提出聚餐費(fèi)用申請(qǐng)的請(qǐng)求后嘴高,該請(qǐng)求會(huì)經(jīng)由項(xiàng)目經(jīng)理、部門經(jīng)理辉懒、總經(jīng)理之中的某一位領(lǐng)導(dǎo)來進(jìn)行相應(yīng)的處理阳惹,但是提出申請(qǐng)的人并不知道最終會(huì)由誰來處理他的請(qǐng)求,一般申請(qǐng)人是把自己的申請(qǐng)?zhí)峤唤o項(xiàng)目經(jīng)理眶俩,或許最后是由總經(jīng)理來處理他的請(qǐng)求莹汤。
  
  可以使用責(zé)任鏈模式來實(shí)現(xiàn)上述功能:當(dāng)某人提出聚餐費(fèi)用申請(qǐng)的請(qǐng)求后,該請(qǐng)求會(huì)在 **項(xiàng)目經(jīng)理—〉部門經(jīng)理—〉總經(jīng)理 **這樣一條領(lǐng)導(dǎo)處理鏈上進(jìn)行傳遞颠印,發(fā)出請(qǐng)求的人并不知道誰會(huì)來處理他的請(qǐng)求纲岭,每個(gè)領(lǐng)導(dǎo)會(huì)根據(jù)自己的職責(zé)范圍,來判斷是處理請(qǐng)求還是把請(qǐng)求交給更高級(jí)別的領(lǐng)導(dǎo)线罕,只要有領(lǐng)導(dǎo)處理了止潮,傳遞就結(jié)束了。
  
需要把每位領(lǐng)導(dǎo)的處理獨(dú)立出來钞楼,實(shí)現(xiàn)成單獨(dú)的職責(zé)處理對(duì)象喇闸,然后為它們提供一個(gè)公共的、抽象的父職責(zé)對(duì)象询件,這樣就可以在客戶端來動(dòng)態(tài)地組合職責(zé)鏈燃乍,實(shí)現(xiàn)不同的功能要求了。

  
/**
 * 抽象的處理類
 */
public abstract class Handler {

    private Handler successor;

    /**
     * 處理金額審批的方法
     * @param money
     */
    public abstract void handleFeeRequest(double money);

    public Handler getSuccessor() {
        return successor;
    }

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
}

/**
 * 具體的處理類宛琅,項(xiàng)目經(jīng)理
 */
public class ProjectManager extends Handler {
    @Override
    public void handleFeeRequest(double money) {
        if(money > 400){
            System.out.println("金額超過400刻蟹,項(xiàng)目經(jīng)理轉(zhuǎn)交請(qǐng)求到下一級(jí)");
            if(getSuccessor() != null){
                getSuccessor().handleFeeRequest(money);
            }
        }else{
            System.out.println("金額未超過400,項(xiàng)目經(jīng)理同意審批");
        }
    }
}

/**
 * 具體的處理類嘿辟,部門經(jīng)理
 */
public class DeptManager extends Handler {
    @Override
    public void handleFeeRequest(double money) {
        if(money > 1000){
            System.out.println("金額超過1000舆瘪,部門經(jīng)理轉(zhuǎn)交請(qǐng)求到下一級(jí)");
            if(getSuccessor() != null){
                getSuccessor().handleFeeRequest(money);
            }
        }else{
            System.out.println("金額未超過1000片效,部門經(jīng)理同意審批");
        }
    }
}

/**
 * 具體的處理類,公司經(jīng)理
 */
public class CompanyManager extends Handler {
    @Override
    public void handleFeeRequest(double money) {
        System.out.println("公司老總處理");
    }
}


/**
 * 客戶端
 */
public class Client {

    public static void main(String[] args) {
        Handler project = new ProjectManager();
        Handler dept = new DeptManager();
        Handler company = new CompanyManager();

        project.setSuccessor(dept);
        dept.setSuccessor(company);

        //最先由項(xiàng)目經(jīng)理審批
        project.handleFeeRequest(100);
        System.out.println("--------------------");
        project.handleFeeRequest(500);
        System.out.println("--------------------");
        project.handleFeeRequest(2000);
        System.out.println("--------------------");
    }

}

純的與不純的責(zé)任鏈模式

一個(gè)純的責(zé)任鏈模式要求一個(gè)具體的處理者對(duì)象只能在兩個(gè)行為中選擇一個(gè):一是承擔(dān)責(zé)任英古,而是把責(zé)任推給下家淀衣。不允許出現(xiàn)某一個(gè)具體處理者對(duì)象在承擔(dān)了一部分責(zé)任后又 把責(zé)任向下傳的情況。

在一個(gè)純的責(zé)任鏈模式里面哺呜,一個(gè)請(qǐng)求必須被某一個(gè)處理者對(duì)象所接收舌缤;在一個(gè)不純的責(zé)任鏈模式里面,一個(gè)請(qǐng)求可以最終不被任何接收端對(duì)象所接收某残。

純的責(zé)任鏈模式的實(shí)際例子很難找到国撵,一般看到的例子均是不純的責(zé)任鏈模式的實(shí)現(xiàn)。有些人認(rèn)為不純的責(zé)任鏈根本不是責(zé)任鏈模式玻墅,這也許是有道理的介牙。但是在實(shí)際的系統(tǒng)里,純的責(zé)任鏈很難找到澳厢。如果堅(jiān)持責(zé)任鏈不純便不是責(zé)任鏈模式环础,那么責(zé)任鏈模式便不會(huì)有太大意義了。

職責(zé)鏈靈活在哪

  1. 改變內(nèi)部的傳遞規(guī)則
    在內(nèi)部剩拢,項(xiàng)目經(jīng)理完全可以跳過人事部到那一關(guān)直接找到總經(jīng)理线得。
    每個(gè)人都可以去動(dòng)態(tài)地指定他的繼任者。

  2. 可以從職責(zé)鏈任何一關(guān)開始徐伐。
    如果項(xiàng)目經(jīng)理不在贯钩,可以直接去找部門經(jīng)理,責(zé)任鏈還會(huì)繼續(xù)办素,沒有影響角雷。

  3. 用與不用的區(qū)別
    不用職責(zé)鏈的結(jié)構(gòu),我們需要和公司中的每一個(gè)層級(jí)都發(fā)生耦合關(guān)系性穿。
    如果反映在代碼上即使我們需要在一個(gè)類中去寫上很多丑陋的if….else語句勺三。
    如果用了職責(zé)鏈,相當(dāng)于我們面對(duì)的是一個(gè)黑箱需曾,我們只需要認(rèn)識(shí)其中的一個(gè)部門吗坚,然后讓黑箱內(nèi)部去負(fù)責(zé)傳遞就好了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市呆万,隨后出現(xiàn)的幾起案子刻蚯,更是在濱河造成了極大的恐慌,老刑警劉巖桑嘶,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異躬充,居然都是意外死亡逃顶,警方通過查閱死者的電腦和手機(jī)讨便,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來以政,“玉大人霸褒,你說我怎么就攤上這事∮” “怎么了废菱?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抖誉。 經(jīng)常有香客問我殊轴,道長,這世上最難降的妖魔是什么袒炉? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任旁理,我火速辦了婚禮,結(jié)果婚禮上我磁,老公的妹妹穿的比我還像新娘孽文。我一直安慰自己,他們只是感情好夺艰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布芋哭。 她就那樣靜靜地躺著,像睡著了一般郁副。 火紅的嫁衣襯著肌膚如雪减牺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天霞势,我揣著相機(jī)與錄音烹植,去河邊找鬼。 笑死愕贡,一個(gè)胖子當(dāng)著我的面吹牛草雕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播固以,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼墩虹,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了憨琳?” 一聲冷哼從身側(cè)響起诫钓,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎篙螟,沒想到半個(gè)月后菌湃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡遍略,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年惧所,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了骤坐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡下愈,死狀恐怖纽绍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情势似,我是刑警寧澤拌夏,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站履因,受9級(jí)特大地震影響障簿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜搓逾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一卷谈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧霞篡,春花似錦世蔗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至余掖,卻和暖如春寸爆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盐欺。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國打工赁豆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冗美。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓魔种,卻偏偏與公主長得像,于是被迫代替她去往敵國和親粉洼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子节预,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容