重識設計模式-責任鏈模式(Chain of Responsibility Pattern)

本文已同步發表到個人技術微信公衆號,掃一掃文章底部的二維碼或在微信搜索 「程序員驛站」便可關注,不按期更新優質技術文章。同時,也歡迎加入QQ技術羣(羣號:650306310)一塊兒交流學習!java

「一對二」,「過」,「過」……這聲音熟悉嗎?你會想到什麼?對!紙牌。在相似「鬥地主」這樣的 紙牌遊戲中,某人出牌給他的下家,下家看看手中的牌,若是要不起上家的牌則將出牌請求 再轉發給他的下家,其下家再進行判斷。一個循環下來,若是其餘人都要不起該牌,則最初 的出牌者能夠打出新的牌。在這個過程當中,牌做爲一個請求沿着一條鏈在傳遞,每一位紙牌 的玩家均可以處理該請求。在設計模式中,咱們也有一種專門用於處理這種請求鏈式傳遞的 模式,它就是下面要介紹的責任鏈模式。程序員

定義

責任鏈模式(Chain of Responsibility Pattern): 也叫職責鏈模式,避免請求發送者與接收者耦合在一塊兒,讓多個對象都有可能接收請求,將這些對象鏈接成一條鏈,而且沿着這條鏈傳遞請求,直到有對象處理它爲止。責任鏈模式是一種對象行爲型模式。設計模式

角色

責任鏈模式結構的核心在於引入了一個抽象處理者。微信

責任鏈模式結構圖

從責任鏈模式結構圖中咱們看到包含以下幾個角色:ide

Handler( 抽象處理者) : 它定義了一個處理請求的接口,通常設計爲抽象類,因爲不一樣的具體處理者處理請求的方式不一樣,所以在其中定義了抽象請求處理方法。由於每個處理者的下家仍是一個處理者,所以在抽象處理者中定義了一個抽象處理者類型的對象(如結構圖中的successor),做爲其對下家的引用。經過該引用,處理者能夠連成一條鏈。性能

ConcreteHandler(具體處理者) : 它是抽象處理者的子類,能夠處理用戶請求,在具體處理者類中實現了抽象處理者中定義的抽象請求處理方法,在處理請求以前須要進行判斷,看是否有相應的處理權限,若是能夠處理請求就處理它,不然將請求轉發給後繼者;在具體處理者中能夠訪問鏈中下一個對象,以便請求的轉發。學習

在責任鏈模式裏,不少對象由每個對象對其下家的引用而鏈接起來造成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。發出這個請求的客戶端並不知道鏈上的哪個對象最終處理這個請求,這使得系統能夠在不影響客戶端的狀況下動態地從新組織鏈和分配責任。測試

案例回放

某軟件公司承接了某企業SCM(Supply Chain Management,供應鏈管理)系統的開發任務,其中包含一個採購審批子系統。該企業的採購審批是分級進行的,即根據採購金額的不一樣由不一樣層次的主管人員來審批,當前主要審批層次爲:this

1.主任能夠審批5萬元如下( 不包括5萬元) 的採購單;
2.副董事長能夠審批5萬元至10萬元( 不包括10萬元) 的採購單;
3.董事長能夠審批10萬元至50萬元( 不包括50萬元) 的採購單;
4.50萬元及以上的採購單就須要開董事會討論決定。spa

整個審批層次圖以下所示:

採購單分級審批示意圖

SCM系統中的採購單審批,主任、副董事長、董事長和董事會均可以處理採購單,他們能夠構成一條處理採購單的鏈式結構,採購單沿着這條鏈進行傳遞,這條鏈就稱爲責任鏈。責任鏈能夠是一條直線、一個環或者一個樹形結構,最多見的職責鏈是直線型,即沿着一條單向的鏈來傳遞請求。鏈上的每個對象都是請求處理者,責任鏈模式能夠將請求的處理者組織成一條鏈,並讓請求沿着鏈傳遞,由鏈上的處理者對請求進行相應的處理,客戶端無須關心請求的處理細節以及請求的傳遞,只需將請求發送到鏈上便可,實現請求發送者和請求處理者解耦。

咱們使用責任鏈模式來實現採購單的分級審批,其基本結構如圖所示:

抽象類Approver充當抽象處理者(抽象傳遞者),Director、VicePresident、President和Congress充當具體處理者(具體傳遞者),PurchaseRequest充當請求類。完整代碼以下所示:

PurchaseRequest.java

/** * Created by chendx on 2019/3/16 * * @since 1.0 * 採購單:請求類 */
public class PurchaseRequest {
    private double amount; //採購金額
    private int number; //採購單編號
    private String purpose; //採購目的

    public PurchaseRequest(double amount, int number, String purpose) {
        this.amount = amount;
        this.number = number;
        this.purpose = purpose;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }

    public double getAmount() {
        return this.amount;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public int getNumber() {
        return this.number;
    }

    public void setPurpose(String purpose) {
        this.purpose = purpose;
    }

    public String getPurpose() {
        return this.purpose;
    }
}
複製代碼

Approver.java

/** 定義後繼對象 */
    protected Approver successor;
    /** 審批者姓名 */
    protected String name;

    public Approver(String name) {
        this.name = name;
    }

    /** * 設置後繼者 */
    public void setSuccessor(Approver successor) {
        this.successor = successor;
    }

    /** * 抽象請求處理方法 */
    public abstract void processRequest(PurchaseRequest request);
複製代碼

Director.java

/** * Created by chendx on 2019/3/16 * * @since 1.0 * 主任類:具體處理者 */
public class Director extends Approver {

    public Director(String name) {
        super(name);
    }

    /** * 具體請求處理方法 */
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 50000) {
            System.out.println("主任" + this.name + "審批採購單:" + request.getNumber()+",金額:"+request.getAmount()+",採購目的:"+request.getPurpose());
        } else {
            //轉發請求
            this.successor.processRequest(request);
        }
    }
}
複製代碼

VicePresident.java'

/** * Created by chendx on 2019/3/16 * * @since 1.0 * 副董事長類:具體處理者 */
public class VicePresident extends Approver {

    public VicePresident(String name) {
        super(name);
    }

    /** * 具體請求處理方法 */
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 100000) {
            System.out.println("副董事長" + this.name + "審批採購單:" + request.getNumber()+",金額:"+request.getAmount()+",採購目的:"+request.getPurpose());
        } else {
            this.successor.processRequest(request); //轉發請求
        }
    }
}
複製代碼

VicePresident.java

/** * Created by chendx on 2019/3/16 * * @since 1.0 * 董事長類:具體處理者 */
public class VicePresident extends Approver {

    public VicePresident(String name) {
        super(name);
    }

    /** * 具體請求處理方法 */
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 100000) {
            System.out.println("副董事長" + this.name + "審批採購單:" + request.getNumber()+",金額:"+request.getAmount()+",採購目的:"+request.getPurpose());
        } else {
            this.successor.processRequest(request); //轉發請求
        }
    }
}
複製代碼

Congress.java

/** * Created by chendx on 2019/3/16 * * @since 1.0 * 董事會類:具體處理者 */
public class Congress extends Approver {

    public Congress(String name) {
        super(name);
    }

    /** * 具體請求處理方法 */
    public void processRequest(PurchaseRequest request) {
        System.out.println("召開董事會審批採購單:" + request.getNumber() + ",金額:" + request.getAmount() + ",採購目的:" + request.getPurpose());
    }
}
複製代碼

Client.java

/** * Created by chendx on 2019/3/16 * * @since 1.0 * 客戶端測試代碼 */
public class Client {
    public static void main(String[] args) {
        //主任:張無忌
        Approver director = new Director("張無忌");
        //副董事長:楊過
        Approver vicePresident = new VicePresident("楊過");
        //董事長:郭靖
        Approver president = new President("郭靖");
        //董事會
        Approver congress = new Congress("董事會");

        //建立職責鏈
        director.setSuccessor(vicePresident);
        vicePresident.setSuccessor(president);
        president.setSuccessor(congress);

        //建立採購單
        PurchaseRequest pr1 = new PurchaseRequest(45000, 10001, "購買倚天劍。");
        director.processRequest(pr1);

        PurchaseRequest pr2 = new PurchaseRequest(60000, 10002, "購買《 葵花寶典》。");
        director.processRequest(pr2);

        PurchaseRequest pr3 = new PurchaseRequest(160000, 10003, "購買《 金剛經》。");
        director.processRequest(pr3);

        PurchaseRequest pr4 = new PurchaseRequest(800000, 10004, "購買桃花島。");
        director.processRequest(pr4);
    }
}
複製代碼

編譯並運行程序,輸出結果以下:

主任張無忌審批採購單:10001,金額:45000.0元,採購目的:購買倚天劍。
副董事長楊過審批採購單:10002,金額:60000.0元,採購目的:購買《 葵花寶典》 。
董事長郭靖審批採購單:10003,金額:160000.0元,採購目的:購買《 金剛經》 。
召開董事會審批採購單:10004,金額:800000.0元,採購目的:購買桃花島。

若是須要在系統增長一個新的具體處理者,如增長一個經理(Manager)角色能夠審批5萬元至8萬元(不包括8萬元)的採購單,須要編寫一個新的具體處理者類Manager,做爲抽象處理者類Approver的子類,實如今Approver類中定義的抽象處理方法,若是採購金額大於等於8萬元,則將請求轉發給下家,代碼以下所示:

Manager.java

/** * Created by chendx on 2019/3/16 * * @since 1.0 * 經理類:具體處理者 */
public class Manager extends Approver {
    public Manager(String name) {
        super(name);
    }

    /** * 具體請求處理方法 */
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 80000) {
            System.out.println("經理" + this.name + "審批採購單:" + +request.getNumber() + ",金額:" + request.getAmount() + ",採購目的:" + request.getPurpose());
        } else {
            this.successor.processRequest(request); //轉發請求
        }
    }
}
複製代碼

因爲鏈的建立過程由客戶端負責,所以增長新的具體處理者類對原有類庫無任何影響,無須修改已有類的源代碼,符合「開閉原則」。

在客戶端代碼中,若是要將新的具體請求處理者應用在系統中,須要建立新的具體處理者對象,而後將該對象加入責任鏈中。修改客戶端測試代碼:

/** * Created by chendx on 2019/3/16 * * @since 1.0 * 客戶端測試代碼 */
public class Client {
    public static void main(String[] args) {
        //主任:張無忌
        Approver director = new Director("張無忌");
        //副董事長:楊過
        Approver vicePresident = new VicePresident("楊過");
        //董事長:郭靖
        Approver president = new President("郭靖");
        //董事會
        Approver congress = new Congress("董事會");
        //經理
        Approver manager = new Manager("黃蓉");

        //建立職責鏈
        director.setSuccessor(manager);
        manager.setSuccessor(vicePresident);
        vicePresident.setSuccessor(president);
        president.setSuccessor(congress);

        //建立採購單
        PurchaseRequest pr1 = new PurchaseRequest(45000, 10001, "購買倚天劍。");
        director.processRequest(pr1);

        PurchaseRequest pr2 = new PurchaseRequest(60000, 10002, "購買《 葵花寶典》。");
        director.processRequest(pr2);

        PurchaseRequest pr3 = new PurchaseRequest(160000, 10003, "購買《 金剛經》。");
        director.processRequest(pr3);

        PurchaseRequest pr4 = new PurchaseRequest(800000, 10004, "購買桃花島。");
        director.processRequest(pr4);
    }
}
複製代碼

從新編譯並運行程序,輸出結果以下:

主任張無忌審批採購單:10001,金額:45000.0元,採購目的:購買倚天劍。
經理黃蓉審批採購單:10002,金額:60000.0元,採購目的:購買《 葵花寶典》 。
董事長郭靖審批採購單:10003,金額:160000.0元,採購目的:購買《 金剛經》 。
召開董事會審批採購單:10004,金額:800000.0元,採購目的:購買桃花島。

典型應用

Android View的事件分發機制就是責任鏈設計模式的應用之一,這裏爲了節省篇幅,就不在分析View的事件分發機制在源碼中的詳細調用流程。View事件分發機制流程圖以下:

優勢

1.責任鏈模式使得一個對象無須知道是其餘哪個對象處理其請求,對象僅需知道該請求會被處理便可,接收者和發送者都沒有對方的明確信息,且鏈中的對象不須要知道鏈的結構,由客戶端負責鏈的建立,下降了系統的耦合度。

2.請求處理對象僅需維持一個指向其後繼者的引用,而不須要維持它對全部的候選處理者的 引用,可簡化對象的相互鏈接。

3.在給對象分派職責時,職責鏈能夠給咱們更多的靈活性,能夠經過在運行時對該鏈進行動態的增長或修改來增長或改變處理一個請求的職責。

4.在系統中增長一個新的具體請求處理者時無須修改原有系統的代碼,只須要在客戶端從新建鏈便可,從這一點來看是符合「開閉原則」的。

缺點

1.因爲一個請求沒有明確的接收者,那麼就不能保證它必定會被處理,該請求可能一直到鏈的末端都得不處處理;一個請求也可能因職責鏈沒有被正確配置而得不處處理。

2.對於比較長的職責鏈,請求的處理可能涉及到多個處理對象,系統性能將受到必定影響,並且在進行代碼調試時不太方便。

3.若是建鏈不當,可能會形成循環調用,將致使系統陷入死循環。

重點

責任鏈模式並不建立職責鏈,職責鏈的建立工做必須由系統的其餘部分來完成,通常是在使用該職責鏈的客戶端中建立職責鏈。責任鏈模式下降了請求的發送端和接收 端之間的耦合,使多個對象都有機會處理這個請求。

使用場景

在如下狀況下能夠考慮使用職責鏈模式:

1.有多個對象能夠處理同一個請求,具體哪一個對象處理該請求待運行時刻再肯定,客戶端只需將請求提交到鏈上,而無須關心請求的處理對象是誰以及它是如何處理的。

2.在不明確指定接收者的狀況下,向多個對象中的一個提交一個請求。

3.可動態指定一組對象處理請求,客戶端能夠動態建立職責鏈來處理請求,還能夠改變鏈中處理者之間的前後次序。

關注個人技術公衆號"程序員驛站",不更新技術文章,微信掃一掃下方二維碼便可關注:

相關文章
相關標籤/搜索