設(shè)計(jì)模式:如何優(yōu)雅地使用責(zé)任鏈模式

責(zé)任鏈模式(Chain of Responsibility Pattern)在《Head First設(shè)計(jì)模式》一書中被稱為“剩下的模式”,其實(shí)使用也是蠻多的。最近在學(xué)習(xí)Netty的過程中用到了責(zé)任鏈模式,在此反過頭來重溫一下責(zé)任鏈模式。

當(dāng)你想要讓一個(gè)以上的對(duì)象有機(jī)會(huì)能夠處理某個(gè)請(qǐng)求的時(shí)候,就使用責(zé)任鏈模式。

一、場景

借用《Head First設(shè)計(jì)模式》書中的典型場景:需要處理四種類型的電子郵件,第一種類型是粉絲寄來的信,表示他們喜歡新推出的游戲;第二種類型是父母寄來的信,他們抱怨孩子總是沉迷游戲而忘記做作業(yè);第三種類型是店家希望在其他地方也擺放糖果機(jī);第四種類型是垃圾郵件?,F(xiàn)在已經(jīng)可以根據(jù)郵件內(nèi)容確定收到的郵件屬于哪種類型,需要設(shè)計(jì)一個(gè)程序來處理這些郵件。

Talk is cheap. Show me the code.直接用代碼來說話吧。

用枚舉來定義四種類型的郵件:

public enum EmailEnum {
    SPAM_EMAIL(1, "Spam_Email"),
    FAN_EMAIL(2, "Fan_Email"),
    COMPLAINT_EMAIL(3, "Complaint_Email"),
    NEW_LOC_EMAIL(4, "New_Loc_Email");

    private Integer code;
    private String desc;

    EmailEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public Integer getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

假設(shè)郵件有兩個(gè)屬性:郵件類型和郵件內(nèi)容,定義郵件:

public class Email {
    int type;
    String content;

    public Email(int type, String content) {
        this.type = type;
        this.content = content;
    }

    public int getType() {
        return type;
    }

    public String getContent() {
        return content;
    }
}

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

如果不采用責(zé)任鏈模式?使用EmailHandler這個(gè)類來統(tǒng)一處理上述四種郵件,程序是這樣子的:

public class EmailHandler {

    public void handleEmai(Email email) {
        if (EmailEnum.SPAM_EMAIL.getCode().equals(email.getType())) {
            // 處理垃圾郵件
            handleSpamEmail(email);
        } else if (EmailEnum.FAN_EMAIL.getCode().equals(email.getType())) {
            // 處理粉絲郵件
            handleFanEmail(email);
        } else if (EmailEnum.COMPLAINT_EMAIL.getCode().equals(email.getType())) {
            // 處理抱怨郵件
            handleComplaintEmail(email);
        } else {
            // 處理新增郵件
            handleNewLocEmail(email);
        }
    }

    private void handleNewLocEmail(Email email) {
        System.out.println("handleNewLocEmail...");
        // 處理代碼省略
    }

    private void handleComplaintEmail(Email email) {
        System.out.println("handleComplaintEmail...");
        // 處理代碼省略
    }

    private void handleFanEmail(Email email) {
        System.out.println("handleFanEmail...");
        // 處理代碼省略
    }

    public void handleSpamEmail(Email email) {
        System.out.println("handleSpamEmail...");
        // 處理代碼省略
    }
}

這個(gè)類雖然強(qiáng)大,但是是不夠優(yōu)雅的:

(1)EmailHandler類較為龐大,各種類型郵件的處理都集中在一個(gè)類中,違反了“單一職責(zé)原則”。

(2)如果之后增加新的郵件類型、刪除某一種郵件類型,或者有其他新功能的拓展,都必須修改源代碼并進(jìn)行嚴(yán)格測試,違反了“開閉原則”。

開放-關(guān)閉原則:類應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。

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

如何進(jìn)行改進(jìn)呢?那就是使用責(zé)任鏈模式,為某個(gè)請(qǐng)求創(chuàng)建一個(gè)對(duì)象鏈。每個(gè)對(duì)象按照順序檢查這個(gè)請(qǐng)求,并對(duì)其處理,或者將它傳遞給鏈中的下一個(gè)對(duì)象。在本例中,當(dāng)收到電子郵件的時(shí)候,先進(jìn)入第一個(gè)處理器SpamHandler,如果SpamHandler無法處理,就將它傳給FanHandler,以此類推...

本例使用責(zé)任鏈模式的結(jié)構(gòu)圖如圖所示:


UML 類圖

Handler是一個(gè)抽象的處理器,是一個(gè)抽象類。抽象類中定義了一個(gè)抽象處理器的對(duì)象successor,通過該引用,可以形成一條責(zé)任鏈。抽象類中還定義了抽象處理請(qǐng)求的的方法handleRequest()。代碼如下:

public abstract class Handler {
    protected Handler successor;

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

    public abstract void handleRequest(Email email);
}

SpamHandler、FanHandler、ComplaintHandler和NewLocHandler是具體的處理器,繼承抽象類Handler,用來處理具體的郵件請(qǐng)求。處理細(xì)節(jié):處理之前要進(jìn)行類型的判斷,看是否能夠處理該請(qǐng)求,如果可以處理就處理,否則就轉(zhuǎn)發(fā)給后繼的處理器去處理。代碼如下:

SpamHandler

public class SpamHandler extends Handler{
    @Override
    public void handleRequest(Email email) {
        if (EmailEnum.SPAM_EMAIL.getCode().equals(email.getType())) {
            //處理請(qǐng)求
            System.out.println("SpamHandler handleRequest...");
        }
        else {
            this.successor.handleRequest(email);  //轉(zhuǎn)發(fā)請(qǐng)求
        }
    }
}

FanHandler

public class FanHandler extends Handler{
    @Override
    public void handleRequest(Email email) {
        if (EmailEnum.FAN_EMAIL.getCode().equals(email.getType())) {
            //處理請(qǐng)求
            System.out.println("FanHandler handleRequest...");
        }
        else {
            this.successor.handleRequest(email);  //轉(zhuǎn)發(fā)請(qǐng)求
        }
    }
}

ComplaintHandler

public class ComplaintHandler extends Handler{
    @Override
    public void handleRequest(Email email) {
        if (EmailEnum.COMPLAINT_EMAIL.getCode().equals(email.getType())) {
            //處理請(qǐng)求
            System.out.println("ComplaintHandler handleRequest...");
        }
        else {
            this.successor.handleRequest(email);  //轉(zhuǎn)發(fā)請(qǐng)求
        }
    }
}

NewLocHandler

public class NewLocHandler extends Handler{
    @Override
    public void handleRequest(Email email) {
        if (EmailEnum.NEW_LOC_EMAIL.getCode().equals(email.getType())) {
            //處理請(qǐng)求
            System.out.println("NewLocHandler handleRequest...");
        }
        else {
            this.successor.handleRequest(email);  //轉(zhuǎn)發(fā)請(qǐng)求
        }
    }
}

需要注意的是,責(zé)任鏈模式并不創(chuàng)建責(zé)任鏈,責(zé)任鏈的創(chuàng)建工作必須由系統(tǒng)的其他部分來完成,一般是在使用該責(zé)任鏈的客戶端中創(chuàng)建責(zé)任鏈。責(zé)任鏈模式降低了請(qǐng)求的發(fā)送端和接收端之間的耦合,使多個(gè)對(duì)象都有機(jī)會(huì)處理這個(gè)請(qǐng)求。下面編寫測試類進(jìn)行測試:

public class Test {
    public static void main(String[] args) {
        // 創(chuàng)建郵件處理請(qǐng)求
        Email email1 = new Email(1,"aaa");
        Email email2 = new Email(2,"bbb");
        Email email3 = new Email(3,"ccc");
        Email email4 = new Email(4,"ddd");
        // 創(chuàng)建Handler
        SpamHandler handler1 = new SpamHandler();
        FanHandler handler2 = new FanHandler();
        ComplaintHandler handler3 = new ComplaintHandler();
        NewLocHandler handler4 = new NewLocHandler();
        // 創(chuàng)建責(zé)任鏈
        handler1.setSuccessor(handler2);
        handler2.setSuccessor(handler3);
        handler3.setSuccessor(handler4);
        // 處理請(qǐng)求
        handler1.handleRequest(email1);
        handler1.handleRequest(email2);
        handler1.handleRequest(email3);
        handler1.handleRequest(email4);
    }
}

在代碼中創(chuàng)建四種類型的郵件用于處理,創(chuàng)建了四種不同的處理器(SpamHandler、FanHandler、ComplaintHandler、NewLocHandler),形成“handler1 -> handler2 -> handler3 -> handler4”的責(zé)任鏈,使用這條責(zé)任鏈處理四種類型的郵件。運(yùn)行結(jié)構(gòu)如下:


image

這樣處理之后,明顯使得請(qǐng)求發(fā)送者和接受者解耦;每個(gè)實(shí)現(xiàn)類都有自己明確且獨(dú)一無二的職責(zé);如果增加一個(gè)類型,只需要再增加一個(gè)具體類去繼承Handler,書寫自己的處理邏輯,在責(zé)任鏈中進(jìn)行添加;如果刪除某種類型的,只需要在構(gòu)建責(zé)任鏈的時(shí)候,把它刪除就可以了,實(shí)現(xiàn)動(dòng)態(tài)增加或者刪除責(zé)任,符合設(shè)計(jì)模式的原則。

四、總結(jié)

責(zé)任鏈模式通過建立一條鏈來組織請(qǐng)求的處理者,請(qǐng)求將沿著鏈進(jìn)行傳遞,請(qǐng)求發(fā)送者無須知道請(qǐng)求在何時(shí)、何處以及如何被處理,實(shí)現(xiàn)了請(qǐng)求發(fā)送者與處理者的解耦。在軟件開發(fā)中,如果遇到有多個(gè)對(duì)象可以處理同一請(qǐng)求時(shí)可以應(yīng)用責(zé)任鏈模式,例如在Web應(yīng)用開發(fā)中創(chuàng)建一個(gè)過濾器鏈來對(duì)請(qǐng)求數(shù)據(jù)進(jìn)行過濾,在工作流系統(tǒng)中實(shí)現(xiàn)公文的分級(jí)審批等等,使用責(zé)任鏈模式可以較好地解決此類問題。

?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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