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

目錄

本文的結(jié)構(gòu)如下:

  • 引言
  • 什么是責(zé)任鏈模式
  • 模式的結(jié)構(gòu)
  • 典型代碼
  • 代碼示例
  • 純與不純的責(zé)任鏈模式
  • 優(yōu)點(diǎn)和缺點(diǎn)
  • 適用環(huán)境
  • 模式應(yīng)用

一、引言

現(xiàn)在的網(wǎng)絡(luò)小說(shuō)套路大概是這樣的,小嘍啰惹上主角,主角霸氣側(cè)漏,打跑了;小嘍啰找來(lái)門中厲害的親爹,親爹上場(chǎng),過(guò)了兩招一看打不過(guò),一起跑路;但這口氣咽不下,一咬牙找來(lái)爺爺(一般是長(zhǎng)老或者宗主),宗主那當(dāng)然厲害了,主角打不過(guò),狼狽逃命,下定功夫苦加修煉又或者奇遇連連,功力大增,殺上山門怒殺全宗,報(bào)仇雪恨。

image

這種自己打不過(guò),找爸爸,爸爸打不過(guò)找爺爺?shù)哪J骄褪秦?zé)任鏈模式。

二、什么是責(zé)任鏈模式

很多情況下,在一個(gè)軟件系統(tǒng)中可以處理某個(gè)請(qǐng)求的對(duì)象不止一個(gè),比如上面跟主角打架,小嘍啰、小嘍啰他爸、小嘍啰他爺都可以跟主角打架,打不打得過(guò)不管(處不處理不管),主角沿著這條鏈進(jìn)行傳遞,這條鏈就稱為職責(zé)鏈。

職責(zé)鏈可以是一條直線、一個(gè)環(huán)或者一個(gè)樹形結(jié)構(gòu),最常見(jiàn)的職責(zé)鏈?zhǔn)侵本€型,即沿著一條單向的鏈來(lái)傳遞請(qǐng)求。鏈上的每一個(gè)對(duì)象都是請(qǐng)求處理者,責(zé)任鏈模式可以將請(qǐng)求的處理者組織成一條鏈,并讓請(qǐng)求沿著鏈傳遞,由鏈上的處理者對(duì)請(qǐng)求進(jìn)行相應(yīng)的處理,客戶端無(wú)須關(guān)心請(qǐng)求的處理細(xì)節(jié)以及請(qǐng)求的傳遞,只需將請(qǐng)求發(fā)送到鏈上即可,實(shí)現(xiàn)請(qǐng)求發(fā)送者和請(qǐng)求處理者解耦。

責(zé)任鏈模式定義如下:

責(zé)任鏈模式(Chain of Responsibility Pattern):避免請(qǐng)求發(fā)送者與接收者耦合在一起,讓多個(gè)對(duì)象都有可能接收請(qǐng)求,將這些對(duì)象連接成一條鏈,并且沿著這條鏈傳遞請(qǐng)求,直到有對(duì)象處理它為止。責(zé)任鏈模式是一種對(duì)象行為型模式。

三、模式的結(jié)構(gòu)

責(zé)任鏈模式結(jié)構(gòu)的核心在于引入了一個(gè)抽象處理者,責(zé)任鏈模式的UML類圖如下:

image

在責(zé)任鏈模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:

  • Handler(抽象處理者):定義一個(gè)處理請(qǐng)求的接口,一般為抽象類,請(qǐng)求處理方法為抽象方法,不同的具體處理者實(shí)現(xiàn)具體的請(qǐng)求處理。因?yàn)槊恳粋€(gè)處理者的下家還是一個(gè)處理者,因此在抽象處理者中定義了一個(gè)抽象處理者類型的對(duì)象,作為其對(duì)下家的引用。通過(guò)該引用,處理者可以連成一條鏈。
  • ConcreteHandler(具體處理者):它是抽象處理者的子類,可以處理用戶請(qǐng)求,在具體處理者類中實(shí)現(xiàn)了抽象處理者中定義的抽象請(qǐng)求處理方法,在處理請(qǐng)求之前需要進(jìn)行判斷,看是否有相應(yīng)的處理權(quán)限,如果可以處理請(qǐng)求就處理它,否則將請(qǐng)求轉(zhuǎn)發(fā)給后繼者;在具體處理者中可以訪問(wèn)鏈中下一個(gè)對(duì)象,以便請(qǐng)求的轉(zhuǎn)發(fā)。

要形成鏈?zhǔn)秸{(diào)用,關(guān)鍵在于每一個(gè)處理者中有一個(gè)下一個(gè)處理者的引用。

四、典型代碼

抽象處理者是核心,典型代碼如下:

public abstract class Handler {
    protected Handler successor;

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

    public Handler getSuccessor(){
        return successor;
    }

    public abstract void handler(String condition);
}

具體處理者繼承抽象處理者,主要作用有兩個(gè):第一是處理請(qǐng)求,如果能夠處理就自己處理了;第二是轉(zhuǎn)發(fā)請(qǐng)求,如果該請(qǐng)求超出了當(dāng)前處理者類的權(quán)限,可以將該請(qǐng)求轉(zhuǎn)發(fā)給下家。具體處理者類典型代碼如下:

public class ConcreteHandlerA extends Handler {
    public void handler(String condition) {
        if ("conditionA".equals(condition)){
            System.out.println("處理請(qǐng)求");
        }else {
            System.out.println("轉(zhuǎn)發(fā)請(qǐng)求");
            successor.handler(condition);
        }
    }
}

public class ConcreteHandlerB extends Handler {
    public void handler(String condition) {
        if ("conditionB".equals(condition)){
            System.out.println("處理請(qǐng)求");
        }else {
            System.out.println("轉(zhuǎn)發(fā)請(qǐng)求");
            successor.handler(condition);
        }
    }
}

責(zé)任鏈模式并不創(chuàng)建職責(zé)鏈,職責(zé)鏈的創(chuàng)建工作必須由系統(tǒng)的其他部分來(lái)完成,一般是在使用該職責(zé)鏈的客戶端中創(chuàng)建職責(zé)鏈。

五、代碼示例

以引言中的找主角打架為例。

5.1、不用責(zé)任鏈模式

public class Protagonist {
    private int forceValue;//主角武力值
    private String name;
    private int age;

    public Protagonist(String name, int age, int forceValue) {
        this.name = name;
        this.age = age;
        this.forceValue = forceValue;
    }

    public void setForceValue(int forceValue) {
        this.forceValue = forceValue;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getForceValue() {
        return forceValue;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

public class FightHandler {
    public void fightWith(Protagonist protagonist){
        if (protagonist.getForceValue() < 100){//主角武力值小于100,自己上
            selfFighting(protagonist);
        }else if (protagonist.getForceValue() < 200){//找爸爸
            fatherFighting(protagonist);
        }else if(protagonist.getForceValue() < 400){
            grandfatherFighting(protagonist);
        } else {
            togetherFighting(protagonist);
        }
    }

    private void  selfFighting(Protagonist protagonist){
        //todo
        System.out.println("我打得" + protagonist.getName() +"屁滾尿流,哭爹喊娘");
    }

    private void fatherFighting(Protagonist protagonist){
        //todo
        System.out.println("我爸打得" + protagonist.getName() +"屁滾尿流,哭爹喊娘");
    }

    private void grandfatherFighting(Protagonist protagonist){
        //todo
        System.out.println("我爺打得" + protagonist.getName() +"屁滾尿流,哭爹喊娘");
    }

    private void togetherFighting(Protagonist protagonist){
        //todo
        System.out.println("我們一起打得" + protagonist.getName() +"屁滾尿流,哭爹喊娘");
    }
}

看到這段代碼,相信都會(huì)發(fā)現(xiàn)一些問(wèn)題存在:

  • FightHandler類較為龐大,所有的打架方法都集中在這個(gè)類中,測(cè)試和維護(hù)不方便,也違背了“單一職責(zé)原則”。
  • 加入主角生死間突然頓悟,武力值得到極大提升,想要找更厲害的人來(lái)和主角打架,又或者調(diào)整同主角打架的武力判斷值,都需要修改源碼,違背了“開閉原則”。
  • 打架的流程固定,顯示小嘍啰自己上,再是他爸上,再是他爺上,如果一開始就他爺上(說(shuō)不定主角死,全書完),需要修改源碼,不靈活。

5.2、使用責(zé)任鏈模式

image

先定義抽象處理者:

public abstract class FightHandler {
    protected FightHandler successor;

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

    public abstract void fightWith(Protagonist protagonist);
}

幾個(gè)具體處理者:

public class SelfFightHandler extends FightHandler {
    public void fightWith(Protagonist protagonist) {
        if (protagonist.getForceValue() < 100){
            //todo
            System.out.println("我打得" + protagonist.getName() +"屁滾尿流,哭爹喊娘");
        }else {
            successor.fightWith(protagonist);
        }
    }
}

public class FatherFightHandler extends FightHandler{
    public void fightWith(Protagonist protagonist) {
        if (protagonist.getForceValue() < 200){
            //todo
            System.out.println("我爸打得" + protagonist.getName() +"屁滾尿流,哭爹喊娘");
        }else {
            successor.fightWith(protagonist);
        }
    }
}

public class GrandfatherFightHandler extends FightHandler {
    public void fightWith(Protagonist protagonist) {
        if (protagonist.getForceValue() < 400){
            //todo
            System.out.println("我爺打得" + protagonist.getName() +"屁滾尿流,哭爹喊娘");
        }else {
            successor.fightWith(protagonist);
        }
    }
}

public class TogetherFightHandler extends FightHandler {
    public void fightWith(Protagonist protagonist) {
        if (protagonist.getForceValue() > 400){
            //todo
            System.out.println("我們一起打得" + protagonist.getName() +"屁滾尿流,哭爹喊娘");
        }else {
            successor.fightWith(protagonist);
        }
    }
}

客戶端測(cè)試:

public class Client {
    public static void main(String[] args) {
        FightHandler selfHandler = new SelfFightHandler();
        FightHandler fatherHandler = new FatherFightHandler();
        FightHandler grandfatherHandler = new GrandfatherFightHandler();
        FightHandler togetherHandler = new TogetherFightHandler();

        //創(chuàng)建職責(zé)鏈
        selfHandler.setSuccessor(fatherHandler);
        fatherHandler.setSuccessor(grandfatherHandler);
        grandfatherHandler.setSuccessor(togetherHandler);

        //主角
        Protagonist zxf = new Protagonist("張小凡", 12, 88);
        selfHandler.fightWith(zxf);

        Protagonist qf = new Protagonist("喬峰", 33, 188);
        selfHandler.fightWith(qf);

        Protagonist zbj = new Protagonist("豬八戒", 500, 288);
        selfHandler.fightWith(zbj);
    }
}

這時(shí)新增一個(gè)具體處理者只需要繼承抽象處理者即可,由于鏈的創(chuàng)建過(guò)程由客戶端負(fù)責(zé),因此增加新的具體處理者類對(duì)原有類庫(kù)無(wú)任何影響,無(wú)須修改已有類的源代碼,符合“開閉原則”。

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

責(zé)任鏈模式可分為純的責(zé)任鏈模式不純的責(zé)任鏈模式兩種:

6.1、純的責(zé)任鏈模式

一個(gè)純的責(zé)任鏈模式要求:

  • 一個(gè)具體處理者對(duì)象只能在“處理請(qǐng)求”和“轉(zhuǎn)發(fā)請(qǐng)求”中兩選一。
  • 一個(gè)請(qǐng)求必須被某一個(gè)處理者對(duì)象所接收,不能出現(xiàn)某個(gè)請(qǐng)求未被任何一個(gè)處理者對(duì)象處理的情況。

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

在一個(gè)不純的責(zé)任鏈模式中則允許:

  • 某個(gè)請(qǐng)求被一個(gè)具體處理者部分處理后再向下傳遞。
  • 或者一個(gè)具體處理者處理完某請(qǐng)求后其后繼處理者可以繼續(xù)處理該請(qǐng)求。
  • 或者一個(gè)請(qǐng)求可以最終不被任何處理者對(duì)象所接收。

純的責(zé)任鏈模式的實(shí)際例子很難找到,一般看到的例子均是不純的責(zé)任鏈模式的實(shí)現(xiàn)。

七、優(yōu)點(diǎn)和缺點(diǎn)

7.1、優(yōu)點(diǎn)

責(zé)任鏈模式的主要優(yōu)點(diǎn)如下:

  • 責(zé)任鏈模式使得一個(gè)對(duì)象無(wú)須知道是其他哪一個(gè)對(duì)象處理其請(qǐng)求,對(duì)象僅需知道該請(qǐng)求會(huì)被處理即可,接收者和發(fā)送者都沒(méi)有對(duì)方的明確信息,且鏈中的對(duì)象不需要知道鏈的結(jié)構(gòu),由客戶端負(fù)責(zé)鏈的創(chuàng)建,降低了系統(tǒng)的耦合度。
  • 請(qǐng)求處理對(duì)象僅需維持一個(gè)指向其后繼者的引用,而不需要維持它對(duì)所有的候選處理者的引用,可簡(jiǎn)化對(duì)象的相互連接。
  • 在給對(duì)象分派職責(zé)時(shí),職責(zé)鏈可以給我們更多的靈活性,可以通過(guò)在運(yùn)行時(shí)對(duì)該鏈進(jìn)行動(dòng)態(tài)的增加或修改來(lái)增加或改變處理一個(gè)請(qǐng)求的職責(zé)。
  • 在系統(tǒng)中增加一個(gè)新的具體請(qǐng)求處理者時(shí)無(wú)須修改原有系統(tǒng)的代碼,只需要在客戶端重新建鏈即可,從這一點(diǎn)來(lái)看是符合“開閉原則”的。

7.2、缺點(diǎn)

責(zé)任鏈模式的主要缺點(diǎn)如下:

  • 由于一個(gè)請(qǐng)求沒(méi)有明確的接收者,那么就不能保證它一定會(huì)被處理,該請(qǐng)求可能一直到鏈的末端都得不到處理;一個(gè)請(qǐng)求也可能因職責(zé)鏈沒(méi)有被正確配置而得不到處理。
  • 對(duì)于比較長(zhǎng)的職責(zé)鏈,請(qǐng)求的處理可能涉及到多個(gè)處理對(duì)象,系統(tǒng)性能將受到一定影響,而且在進(jìn)行代碼調(diào)試時(shí)不太方便。
  • 如果建鏈不當(dāng),可能會(huì)造成循環(huán)調(diào)用,將導(dǎo)致系統(tǒng)陷入死循環(huán)。

八、適用環(huán)境

在以下情況下可以考慮使用責(zé)任鏈模式:

  • 有多個(gè)對(duì)象可以處理同一個(gè)請(qǐng)求,具體哪個(gè)對(duì)象處理該請(qǐng)求待運(yùn)行時(shí)刻再確定,客戶端只需將請(qǐng)求提交到鏈上,而無(wú)須關(guān)心請(qǐng)求的處理對(duì)象是誰(shuí)以及它是如何處理的。
  • 在不明確指定接收者的情況下,向多個(gè)對(duì)象中的一個(gè)提交請(qǐng)求。
  • 可動(dòng)態(tài)指定一組對(duì)象處理請(qǐng)求,客戶端可以動(dòng)態(tài)創(chuàng)建職責(zé)鏈來(lái)處理請(qǐng)求,還可以改變鏈中處理者之間的先后次序。

九、模式應(yīng)用

責(zé)任鏈設(shè)計(jì)模式(Chain of Responsibility)的應(yīng)用有:Java Web中的過(guò)濾器鏈、Struts2中的攔截器棧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容