設(shè)計(jì)模式--責(zé)任鏈模式(Chain of Responsibility)

責(zé)任鏈模式(Chain of Responsibility)

在現(xiàn)實(shí)生活中铺浇,常常會(huì)出現(xiàn)這樣的事例:一個(gè)請(qǐng)求有多個(gè)對(duì)象可以處理,但每個(gè)對(duì)象的處理?xiàng)l件或權(quán)限不同明郭。例如嬉挡,公司員工請(qǐng)假,可批假的領(lǐng)導(dǎo)有部門(mén)負(fù)責(zé)人喷户、副總經(jīng)理擂橘、總經(jīng)理等,但每個(gè)領(lǐng)導(dǎo)能批準(zhǔn)的天數(shù)不同摩骨,員工必須根據(jù)自己要請(qǐng)假的天數(shù)去找不同的領(lǐng)導(dǎo)簽名,也就是說(shuō)員工必須記住每個(gè)領(lǐng)導(dǎo)的姓名朗若、電話和地址等信息恼五,這增加了難度。這樣的例子還有很多哭懈,如找領(lǐng)導(dǎo)出差報(bào)銷灾馒、生活中的“擊鼓傳花”游戲等。

在計(jì)算機(jī)軟硬件中也有相關(guān)例子遣总,如總線網(wǎng)中數(shù)據(jù)報(bào)傳送睬罗,每臺(tái)計(jì)算機(jī)根據(jù)目標(biāo)地址是否同自己的地址相同來(lái)決定是否接收轨功;還有異常處理中,處理程序根據(jù)異常的類型決定自己是否處理該異常容达;還有Struts2的攔截器古涧、JSP和Servlet的 Filter 等,所有這些花盐,如果用責(zé)任鏈模式都能很好解決羡滑。

模式的定義與特點(diǎn)

  • 責(zé)任鏈(Chain of Responsibility)模式的定義:
    為了避免請(qǐng)求發(fā)送者與多個(gè)請(qǐng)求處理者耦合在一起,將所有請(qǐng)求的處理者通過(guò)前一對(duì)象記住其下一個(gè)對(duì)象的引用而連成一條鏈算芯;當(dāng)有請(qǐng)求發(fā)生時(shí)柒昏,可將請(qǐng)求沿著這條鏈傳遞,直到有對(duì)象處理它為止熙揍。

\color{red}{注意:責(zé)任鏈模式也叫職責(zé)鏈模式职祷。}

在責(zé)任鏈模式中,客戶只需要將請(qǐng)求發(fā)送到責(zé)任鏈上即可届囚,無(wú)須關(guān)心請(qǐng)求的處理細(xì)節(jié)和請(qǐng)求的傳遞過(guò)程有梆,所以責(zé)任鏈將請(qǐng)求的發(fā)送者和請(qǐng)求的處理者解耦了。責(zé)任鏈模式是一種對(duì)象行為型模式奖亚。

  • 責(zé)任鏈模式(Chain of Responsibility)的優(yōu)點(diǎn):
  1. 降低了對(duì)象之間的耦合度淳梦。該模式使得一個(gè)對(duì)象無(wú)須知道到底是哪一個(gè)對(duì)象處理其請(qǐng)求以及鏈的結(jié)構(gòu),發(fā)送者和接收者也無(wú)須擁有對(duì)方的明確信息昔字。
  2. 增強(qiáng)了系統(tǒng)的可擴(kuò)展性爆袍。可以根據(jù)需要增加新的請(qǐng)求處理類作郭,滿足開(kāi)閉原則陨囊。
  3. 增強(qiáng)了給對(duì)象指派職責(zé)的靈活性。當(dāng)工作流程發(fā)生變化夹攒,可以動(dòng)態(tài)地改變鏈內(nèi)的成員或者調(diào)動(dòng)它們的次序蜘醋,也可動(dòng)態(tài)地新增或者刪除責(zé)任。
  4. 責(zé)任鏈簡(jiǎn)化了對(duì)象之間的連接咏尝。每個(gè)對(duì)象只需保持一個(gè)指向其后繼者的引用压语,不需保持其他所有處理者的引用,這避免了使用眾多的 if 或者 if···else 語(yǔ)句编检。
  5. 責(zé)任分擔(dān)胎食。每個(gè)類只需要處理自己該處理的工作,不該處理的傳遞給下一個(gè)對(duì)象完成允懂,明確各類的責(zé)任范圍厕怜,符合類的單一職責(zé)原則。
  • 責(zé)任鏈模式(Chain of Responsibility)的缺點(diǎn):
  1. 不能保證每個(gè)請(qǐng)求一定被處理。由于一個(gè)請(qǐng)求沒(méi)有明確的接收者粥航,所以不能保證它一定會(huì)被處理琅捏,該請(qǐng)求可能一直傳到鏈的末端都得不到處理。
  2. 對(duì)比較長(zhǎng)的職責(zé)鏈递雀,請(qǐng)求的處理可能涉及多個(gè)處理對(duì)象柄延,系統(tǒng)性能將受到一定影響。
  3. 職責(zé)鏈建立的合理性要靠客戶端來(lái)保證映之,增加了客戶端的復(fù)雜性拦焚,可能會(huì)由于職責(zé)鏈的錯(cuò)誤設(shè)置而導(dǎo)致系統(tǒng)出錯(cuò),如可能會(huì)造成循環(huán)調(diào)用杠输。

模式的結(jié)構(gòu)與實(shí)現(xiàn)

通常情況下赎败,可以通過(guò)數(shù)據(jù)鏈表來(lái)實(shí)現(xiàn)職責(zé)鏈模式的數(shù)據(jù)結(jié)構(gòu)。

1.模式的結(jié)構(gòu)

職責(zé)鏈模式主要包含以下角色蠢甲。

  1. 抽象處理者(Handler)角色: 定義一個(gè)處理請(qǐng)求的接口僵刮,包含抽象處理方法和一個(gè)后繼連接。
  2. 具體處理者(Concrete Handler)角色: 實(shí)現(xiàn)抽象處理者的處理方法鹦牛,判斷能否處理本次請(qǐng)求搞糕,如果可以處理請(qǐng)求則處理,否則將該請(qǐng)求轉(zhuǎn)給它的后繼者曼追。
  3. 客戶類(Client)角色: 創(chuàng)建處理鏈窍仰,并向鏈頭的具體處理者對(duì)象提交請(qǐng)求,它不關(guān)心處理細(xì)節(jié)和請(qǐng)求的傳遞過(guò)程礼殊。

其結(jié)構(gòu)圖如圖 1 所示驹吮。客戶端可按圖 2 所示設(shè)置責(zé)任鏈晶伦。


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

圖2 責(zé)任鏈
2. 模式的實(shí)現(xiàn)

職責(zé)鏈模式的實(shí)現(xiàn)代碼如下:

package chainOfResponsibility;
public class ChainOfResponsibilityPattern
{
    public static void main(String[] args)
    {
        //組裝責(zé)任鏈 
        Handler handler1=new ConcreteHandler1(); 
        Handler handler2=new ConcreteHandler2(); 
        handler1.setNext(handler2); 
        //提交請(qǐng)求 
        handler1.handleRequest("two");
    }
}
//抽象處理者角色
abstract class Handler
{
    private Handler next;
    public void setNext(Handler next)
    {
        this.next=next; 
    }
    public Handler getNext()
    { 
        return next; 
    }   
    //處理請(qǐng)求的方法
    public abstract void handleRequest(String request);       
}
//具體處理者角色1
class ConcreteHandler1 extends Handler
{
    public void handleRequest(String request)
    {
        if(request.equals("one")) 
        {
            System.out.println("具體處理者1負(fù)責(zé)處理該請(qǐng)求碟狞!");       
        }
        else
        {
            if(getNext()!=null) 
            {
                getNext().handleRequest(request);             
            }
            else
            {
                System.out.println("沒(méi)有人處理該請(qǐng)求!");
            }
        } 
    } 
}
//具體處理者角色2
class ConcreteHandler2 extends Handler
{
    public void handleRequest(String request)
    {
        if(request.equals("two")) 
        {
            System.out.println("具體處理者2負(fù)責(zé)處理該請(qǐng)求婚陪!");       
        }
        else
        {
            if(getNext()!=null) 
            {
                getNext().handleRequest(request);             
            }
            else
            {
                System.out.println("沒(méi)有人處理該請(qǐng)求族沃!");
            }
        } 
    }
}

程序運(yùn)行結(jié)果如下:

具體處理者2負(fù)責(zé)處理該請(qǐng)求!

模式的應(yīng)用實(shí)例

【例】用責(zé)任鏈模式設(shè)計(jì)一個(gè)請(qǐng)假條審批模塊泌参。

分析:假如規(guī)定學(xué)生請(qǐng)假小于或等于 2 天脆淹,班主任可以批準(zhǔn);小于或等于 7 天沽一,系主任可以批準(zhǔn)盖溺;小于或等于 10 天,院長(zhǎng)可以批準(zhǔn)锯玛;其他情況不予批準(zhǔn);這個(gè)實(shí)例適合使用職責(zé)鏈模式實(shí)現(xiàn)。

首先攘残,定義一個(gè)領(lǐng)導(dǎo)類(Leader)拙友,它是抽象處理者,包含了一個(gè)指向下一位領(lǐng)導(dǎo)的指針 next 和一個(gè)處理假條的抽象處理方法 handleRequest(int LeaveDays)歼郭;然后遗契,定義班主任類(ClassAdviser)、系主任類(DepartmentHead)和院長(zhǎng)類(Dean)病曾,它們是抽象處理者的子類牍蜂,是具體處理者,必須根據(jù)自己的權(quán)力去實(shí)現(xiàn)父類的 handleRequest(int LeaveDays) 方法泰涂,如果無(wú)權(quán)處理就將假條交給下一位具體處理者鲫竞,直到最后;客戶類負(fù)責(zé)創(chuàng)建處理鏈逼蒙,并將假條交給鏈頭的具體處理者(班主任)从绘。圖 3 所示是其結(jié)構(gòu)圖。


圖3 請(qǐng)假條審批模塊的結(jié)構(gòu)圖

程序代碼如下:

package chainOfResponsibility;
public class LeaveApprovalTest
{
    public static void main(String[] args)
    {
        //組裝責(zé)任鏈 
        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);
        //提交請(qǐng)求 
        teacher1.handleRequest(8);
    }
}
//抽象處理者:領(lǐng)導(dǎo)類
abstract class Leader
{
    private Leader next;
    public void setNext(Leader next)
    {
        this.next=next; 
    }
    public Leader getNext()
    { 
        return next; 
    }   
    //處理請(qǐng)求的方法
    public abstract void handleRequest(int LeaveDays);       
}
//具體處理者1:班主任類
class ClassAdviser extends Leader
{
    public void handleRequest(int LeaveDays)
    {
        if(LeaveDays<=2) 
        {
            System.out.println("班主任批準(zhǔn)您請(qǐng)假" + LeaveDays + "天是牢。");       
        }
        else
        {
            if(getNext() != null) 
            {
                getNext().handleRequest(LeaveDays);             
            }
            else
            {
                  System.out.println("請(qǐng)假天數(shù)太多僵井,沒(méi)有人批準(zhǔn)該假條!");
            }
        } 
    } 
}
//具體處理者2:系主任類
class DepartmentHead extends Leader
{
    public void handleRequest(int LeaveDays)
    {
        if(LeaveDays<=7) 
        {
            System.out.println("系主任批準(zhǔn)您請(qǐng)假" + LeaveDays + "天驳棱。");       
        }
        else
        {
            if(getNext() != null) 
            {
                  getNext().handleRequest(LeaveDays);             
            }
            else
            {
                System.out.println("請(qǐng)假天數(shù)太多批什,沒(méi)有人批準(zhǔn)該假條!");
           }
        } 
    } 
}
//具體處理者3:院長(zhǎng)類
class Dean extends Leader
{
    public void handleRequest(int LeaveDays)
    {
        if(LeaveDays<=10) 
        {
            System.out.println("院長(zhǎng)批準(zhǔn)您請(qǐng)假" + LeaveDays + "天社搅。");       
        }
        else
        {
              if(getNext() != null) 
            {
                getNext().handleRequest(LeaveDays);             
            }
            else
            {
                  System.out.println("請(qǐng)假天數(shù)太多驻债,沒(méi)有人批準(zhǔn)該假條!");
            }
        } 
    } 
}
//具體處理者4:教務(wù)處長(zhǎng)類
class DeanOfStudies extends Leader
{
    public void handleRequest(int LeaveDays)
    {
        if(LeaveDays<=20) 
        {
            System.out.println("教務(wù)處長(zhǎng)批準(zhǔn)您請(qǐng)假"+LeaveDays+"天罚渐。");       
        }
        else
        {
              if(getNext()!=null) 
            {
                getNext().handleRequest(LeaveDays);          
            }
            else
            {
                  System.out.println("請(qǐng)假天數(shù)太多却汉,沒(méi)有人批準(zhǔn)該假條!");
            }
        } 
    } 
}

程序運(yùn)行結(jié)果如下:

院長(zhǎng)批準(zhǔn)您請(qǐng)假8天荷并。

假如增加一個(gè)教務(wù)處長(zhǎng)類合砂,可以批準(zhǔn)學(xué)生請(qǐng)假 20 天,也非常簡(jiǎn)單源织,代碼如下:

//具體處理者4:教務(wù)處長(zhǎng)類
class DeanOfStudies extends Leader
{
    public void handleRequest(int LeaveDays)
    {
        if(LeaveDays<=20)
        {
            System.out.println("教務(wù)處長(zhǎng)批準(zhǔn)您請(qǐng)假"+LeaveDays+"天翩伪。");
        }
        else
        {
            if(getNext()!=null)
            {
                getNext().handleRequest(LeaveDays);
            }
            else
            {
                System.out.println("請(qǐng)假天數(shù)太多,沒(méi)有人批準(zhǔn)該假條谈息!");
            }
        }
    }
}

模式的應(yīng)用場(chǎng)景

  • 有多個(gè)對(duì)象可以處理一個(gè)請(qǐng)求缘屹,哪個(gè)對(duì)象處理該請(qǐng)求由運(yùn)行時(shí)刻自動(dòng)確定。
  • 可動(dòng)態(tài)指定一組對(duì)象處理請(qǐng)求侠仇,或添加新的處理者轻姿。
  • 在不明確指定請(qǐng)求處理者的情況下犁珠,向多個(gè)處理者中的一個(gè)提交請(qǐng)求。

模式的擴(kuò)展

職責(zé)鏈模式存在以下兩種情況互亮。

  • 純的職責(zé)鏈模式:一個(gè)請(qǐng)求必須被某一個(gè)處理者對(duì)象所接收犁享,且一個(gè)具體處理者對(duì)某個(gè)請(qǐng)求的處理只能采用以下兩種行為之一:自己處理(承擔(dān)責(zé)任);把責(zé)任推給下家處理豹休。
  • 不純的職責(zé)鏈模式:允許出現(xiàn)某一個(gè)具體處理者對(duì)象在承擔(dān)了請(qǐng)求的一部分責(zé)任后又將剩余的責(zé)任傳給下家的情況炊昆,且一個(gè)請(qǐng)求可以最終不被任何接收端對(duì)象所接收。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末威根,一起剝皮案震驚了整個(gè)濱河市凤巨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌洛搀,老刑警劉巖敢茁,帶你破解...
    沈念sama閱讀 222,865評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異姥卢,居然都是意外死亡卷要,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)独榴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)僧叉,“玉大人,你說(shuō)我怎么就攤上這事棺榔∑慷椋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,631評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵症歇,是天一觀的道長(zhǎng)郎笆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)忘晤,這世上最難降的妖魔是什么宛蚓? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,199評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮设塔,結(jié)果婚禮上凄吏,老公的妹妹穿的比我還像新娘。我一直安慰自己闰蛔,他們只是感情好痕钢,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,196評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著序六,像睡著了一般任连。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上例诀,一...
    開(kāi)封第一講書(shū)人閱讀 52,793評(píng)論 1 314
  • 那天随抠,我揣著相機(jī)與錄音裁着,去河邊找鬼。 笑死拱她,一個(gè)胖子當(dāng)著我的面吹牛跨算,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播椭懊,決...
    沈念sama閱讀 41,221評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼步势!你這毒婦竟也來(lái)了氧猬?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,174評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤坏瘩,失蹤者是張志新(化名)和其女友劉穎盅抚,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體倔矾,經(jīng)...
    沈念sama閱讀 46,699評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妄均,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,770評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哪自。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丰包。...
    茶點(diǎn)故事閱讀 40,918評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖壤巷,靈堂內(nèi)的尸體忽然破棺而出邑彪,到底是詐尸還是另有隱情,我是刑警寧澤胧华,帶...
    沈念sama閱讀 36,573評(píng)論 5 351
  • 正文 年R本政府宣布寄症,位于F島的核電站,受9級(jí)特大地震影響矩动,放射性物質(zhì)發(fā)生泄漏有巧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,255評(píng)論 3 336
  • 文/蒙蒙 一悲没、第九天 我趴在偏房一處隱蔽的房頂上張望篮迎。 院中可真熱鬧,春花似錦檀训、人聲如沸柑潦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,749評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)渗鬼。三九已至,卻和暖如春荧琼,著一層夾襖步出監(jiān)牢的瞬間譬胎,已是汗流浹背差牛。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,862評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留堰乔,地道東北人偏化。 一個(gè)月前我還...
    沈念sama閱讀 49,364評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像镐侯,于是被迫代替她去往敵國(guó)和親侦讨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,926評(píng)論 2 361

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