設計模式-責任鏈模式

關注公衆號 JavaStorm 便於隨時閱讀接收最新干貨java

責任鏈模式

定義

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

用通俗的話就是推卸責任某件事,咱們去解決,找到A,結果A踢皮球,說這不關個人事,去找B解決,而後咱們就去找B,結果B也說,這跟我不要緊,快去找C,就這樣,咱們就被踢來踢去,這就是責任鏈模式的思想。設計模式

角色

Handler(抽象處理者):它定義了處理請求的接口、通常設計成抽象類,持有一個處理器的引用,若是本身沒法處理請求,則轉給下一個處理器。所以在抽象處理者中定義了一個抽象處理者類型的對象,做爲其對下家的引用。經過該引用,處理者能夠連成一條鏈。bash

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

純責任鏈模式

  • 一個具體處理對象只能選擇選擇其中一個處理器:要麼承擔所有責任,要麼把處理責任推給下家,不容許出現某個處理器處理了部分又繼續向下傳遞。
  • 一個請求必須被某一個處理者對象所接收,不能出現某個請求未被任何一個處理者對象處理的狀況

不純的責任鏈模式

  • 容許某個請求被一個具體處理器處理後再向下傳遞
  • 一個具體處理器處理完某請求後還能夠繼續處理該請求。
  • 一個請求最終能夠不被任何處理器所處理。

代碼示例

純責任鏈模式

如今模擬一個場景請求,當數字符合奇數、指定數字、某個下限等可處理,不然就處理失敗。性能

首先咱們定義抽象處理器,以及處理方法模板,定義了當前請求哪些能夠處理,不能處理就轉給next。this

package com.zero.headfirst.chain.support;

import java.util.Objects;

/** * 抽象處理器:定義了抽象請求處理方法,持有一個抽象處理器的引用,用於生成一條處理鏈 */
public abstract class AbstractSupport {
    private String name;
    private AbstractSupport next;

    /** * 定義處理請求的算法模板 */
    public final void process(NumberData numberData) {
        if (resolve(numberData)) {
            done(numberData);
        } else if (Objects.nonNull(next)) {
            next.process(numberData);
        } else {
            fail(numberData);
        }
    }

    /** * 沒法處理 * @param numberData */
    protected void fail(NumberData numberData) {
        System.out.println(numberData + " cannot be resolved.");
    }

    /** * 成功處理 * @param numberData */
    protected void done(NumberData numberData) {
        System.out.println(numberData + " is resolved by " + this + ".");
    }

    /** * 判斷是否能處理,子類重寫 * @param numberData * @return */
    protected abstract boolean resolve(NumberData numberData);

    public AbstractSupport setNext(AbstractSupport next) {
        this.next = next;
        return next;
    }

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

    @Override
    public String toString() {
        return "AbstractSupport{" +
                "name='" + name + '\'' +
                '}';
    }
}

複製代碼

而後定義咱們的具體處理器,NoSupport 是一個永遠不解決,只管推卸責任的類。spa

package com.zero.headfirst.chain.support;

/** * 永遠不解決,返回false */
public class NoSupport extends AbstractSupport {
    public NoSupport(String name) {
        super(name);
    }

    @Override
    protected boolean resolve(NumberData numberData) {
        return false;
    }
}

複製代碼

LimitSupport 解決指定範圍的請求設計

package com.zero.headfirst.chain.support;

/** * 解決指定區間 */
public class LimitSupport extends AbstractSupport {

    private Integer limit;

    public LimitSupport(String name, Integer limit) {
        super(name);
        this.limit = limit;
    }

    @Override
    protected boolean resolve(NumberData numberData) {
        return numberData.getNumber() < limit;
    }
}

複製代碼

OddSupport 類,解決奇數的問題調試

package com.zero.headfirst.chain.support;

/** * 奇數處理 */
public class OddSupport extends AbstractSupport {
    public OddSupport(String name) {
        super(name);
    }

    @Override
    protected boolean resolve(NumberData numberData) {
        return numberData.getNumber() % 2 == 1;
    }
}

複製代碼

SpecialSupport 指定數字處理器

package com.zero.headfirst.chain.support;

/** * 指定數字特殊處理 */
public class SpecialSupport extends AbstractSupport {
   private Integer number;

    public SpecialSupport(String name, Integer number) {
        super(name);
        this.number = number;
    }

    @Override
    protected boolean resolve(NumberData numberData) {
        return numberData.getNumber().equals(number);
    }
}

複製代碼

最後咱們定義一個客戶端去組裝咱們的責任鏈,將全部的處理器連在一塊兒,而且模擬請求

package com.zero.headfirst.chain.support;

public class ChainTest {
    public static void main(String[] args) {
        AbstractSupport alice = new NoSupport("Alice");
        AbstractSupport jams = new OddSupport("jams");
        AbstractSupport bob = new LimitSupport("Bob", 100);
        AbstractSupport charlie = new SpecialSupport("Charlie", 429);

        //定義責任鏈
        alice.setNext(jams).setNext(bob).setNext(charlie);
        for (int i = 0; i < 500; i += 33) {
            alice.process(new NumberData(i));
        }
    }
}

複製代碼

咱們的打印結果:除了咱們定義處理器能處理的數字能夠處理完成,其餘數字沒法處理。

NumberData{number=0} is resolved by AbstractSupport{name='Bob'}.
NumberData{number=33} is resolved by AbstractSupport{name='jams'}.
NumberData{number=66} is resolved by AbstractSupport{name='Bob'}.
NumberData{number=99} is resolved by AbstractSupport{name='jams'}.
NumberData{number=132} cannot be resolved.
NumberData{number=165} is resolved by AbstractSupport{name='jams'}.
NumberData{number=198} cannot be resolved.
NumberData{number=231} is resolved by AbstractSupport{name='jams'}.
NumberData{number=264} cannot be resolved.
NumberData{number=297} is resolved by AbstractSupport{name='jams'}.
NumberData{number=330} cannot be resolved.
NumberData{number=363} is resolved by AbstractSupport{name='jams'}.
NumberData{number=396} cannot be resolved.
NumberData{number=429} is resolved by AbstractSupport{name='jams'}.
NumberData{number=462} cannot be resolved.
NumberData{number=495} is resolved by AbstractSupport{name='jams'}.
複製代碼

不純的責任鏈模式

好比說公司請假須要審批,舉個不恰當的例子,若是請假小於3天,主管審批;4-10天的, 經理審批;11-30天的,總經理審批;超過30天的,不批准等等。這就得一步步去判斷, 若是撇開設計模式不看的話,那麼咱們可使用if…else…把它解決了,可是問題可想而知, 實際中的複雜程度時遠遠超過這個例子的。

如今咱們用責任鏈設計模式解決,將每種角色鏈接起來處理請假數據流。

抽象處理器

package com.zero.design.actions.dutychain;

/** * 責任鏈抽象類,包含核心處理方法,以及後繼責任處理器設置.由不一樣的處理器繼承 */
public abstract class AbstractLeaderHandler {

    protected String handlerName;

    /** * 責任鏈上的後繼對象,即這個對象沒法處理,就轉移給下一個Leader */
    protected AbstractLeaderHandler nextLeaderHandler;

    public AbstractLeaderHandler(String handlerName) {
        this.handlerName = handlerName;
    }

    /** * 處理請求的核心的業務方法 * 須要不一樣繼承該類的處理器本身實現 * @param request */
    public abstract void handleRequest(LeaveRequest request);

    /** * 設定責任鏈上的後繼對象 * @param nextLeaderHandler */
    public AbstractLeaderHandler setNextLeaderHandler(AbstractLeaderHandler nextLeaderHandler) {
        this.nextLeaderHandler = nextLeaderHandler;
        return nextLeaderHandler;
    }
}

複製代碼

具體處理器-主任處理器

負責處理 3 天如下的假期,大於三天的遞交給主管

package com.zero.design.actions.dutychain;

/** * 主任處理器,繼承抽象處理器接口,處理相應業務或設置下一個處理器 */
public class DirectorHandler extends AbstractLeaderHandler {


    public DirectorHandler(String handlerName) {
        super(handlerName);
    }

    @Override
    public void handleRequest(LeaveRequest request) {
        int days = request.getLeaveDays(); //獲取請假天數
        String name = request.getName(); //獲取請假人姓名
        String reason = request.getReason(); // 獲取請假理由

        if (days <= 3) {
            //若是知足3天內的要求,主任直接審批
            System.out.println("員工" + name + "請假" + days + "天,理由:" + reason);
            System.out.println("主任" + this.handlerName + "審批經過");
        } else {
            System.out.println("請假天數過多,主任" + this.handlerName + "處理結束,還要交給經理處理");
            if(this.nextLeaderHandler != null) {
                //不然,若是鏈上存在下一個Leader,就讓他處理
                this.nextLeaderHandler.handleRequest(request);
            }
        }
    }
}


複製代碼

經理處理器

處理小於10天的假期,大於10則遞交給總辦

package com.zero.design.actions.dutychain;

/** * 經理處理器 */
public class ManagerHandler extends AbstractLeaderHandler {

    public ManagerHandler(String handlerName) {
        super(handlerName);
    }

    @Override
    public void handleRequest(LeaveRequest request) {
        int days = request.getLeaveDays(); //獲取請假天數
        String name = request.getName(); //獲取請假人姓名
        String reason = request.getReason(); // 獲取請假理由

        if(days <= 10) { //若是知足10天內的要求,經理直接審批
            System.out.println("員工" + name + "請假" + days + "天,理由:" + reason);
            System.out.println("經理" + this.handlerName + "審批經過");
        } else {
            System.out.println("請假天數過多,經理" + this.handlerName + "處理結束,交給總辦處理");
            if(this.nextLeaderHandler != null) { //不然,若是鏈上存在下一個Leader,就讓他處理
                this.nextLeaderHandler.handleRequest(request);
            }
        }
    }
}


複製代碼

總辦處理器

處理大於 10 天假期的請求

package com.zero.design.actions.dutychain;

/** * 總辦處理器 */
public class GeneralManagerHandler extends AbstractLeaderHandler {

    public GeneralManagerHandler(String handlerName) {
        super(handlerName);
    }

    @Override
    public void handleRequest(LeaveRequest request) {
        int days = request.getLeaveDays(); //獲取請假天數
        String name = request.getName(); //獲取請假人姓名
        String reason = request.getReason(); // 獲取請假理由

        if(days <= 30) { //若是知足10天內的要求,經理直接審批
            System.out.println("員工" + name + "請假" + days + "天,理由:" + reason);
            System.out.println("總辦" + this.handlerName + "審批經過");
        } else {
            System.out.println("請假天數過多,總辦" + this.handlerName + "無法處理");
            if(this.nextLeaderHandler != null) { //不然,若是鏈上存在下一個Leader,就讓他處理
                this.nextLeaderHandler.handleRequest(request);
            } else {
                System.out.println("請假不成功");
            }
        }
    }
}


複製代碼

客戶端角色

聲明一條處理鏈,模擬請假數據流

package com.zero.design.actions.dutychain;

/** * 模擬請求: * 他們處理請求的條件是不一樣的,並且只要不是本身處理範圍以內的,就會將請求傳給鏈上的下一位來處理 */
public class Client {

    public static void main(String[] args) {
        //初始化責任鏈,應該使用單例
        AbstractLeaderHandler directorHandler = new DirectorHandler("張勝男");
        AbstractLeaderHandler managerHandler = new ManagerHandler("李四");
        AbstractLeaderHandler generalManagerHndler = new GeneralManagerHandler("趙王");

        //組織好責任對象關係
        directorHandler.setNextLeaderHandler(managerHandler).setNextLeaderHandler(generalManagerHndler);
        //開始請假操做
        LeaveRequest request = new LeaveRequest("倪升武", 15, "在家睡覺");
        directorHandler.handleRequest(request);
    }
}


複製代碼

最後咱們看打印結果

請假天數過多,主任張勝男處理結束,還要交給經理處理
請假天數過多,經理李四處理結束,交給總辦處理
員工倪升武請假15天,理由:在家睡覺
總辦趙王審批經過

複製代碼

總結

責任鏈模式的優勢

  • 對象僅需知道該請求會被處理便可,且鏈中的對象不須要知道鏈的結構,由客戶端負責鏈的建立,下降了系統的耦合度
  • 請求處理對象僅需維持一個指向其後繼者的引用,而不須要維持它對全部的候選處理者的引用,可簡化對象的相互鏈接
  • 在給對象分派職責時,職責鏈能夠給咱們更多的靈活性,能夠在運行時對該鏈進行動態的增刪改,改變處理一個請求的職責
  • 新增一個新的具體請求處理者時無須修改原有代碼,只須要在客戶端從新建鏈便可,符合 "開閉原則"

缺點

  • 一個請求可能因職責鏈沒有被正確配置而得不處處理
  • 對於比較長的職責鏈,請求的處理可能涉及到多個處理對象,系統性能將受到必定影響,且不方便調試
  • 可能由於職責鏈建立不當,形成循環調用,致使系統陷入死循環

使用場景

  1. Netty 中的 PipelineChannelHandler 經過責任鏈設計模式來組織代碼邏輯
  2. Tomcat 過濾器中的責任鏈模式。
  3. Mybatis 中的 Plugin 機制使用了責任鏈模式,配置各類官方或者自定義的 Plugin,與 Filter 相似,能夠在執行 Sql 語句的時候作一些操做
  • 有多個對象能夠處理同一個請求,具體哪一個對象處理該請求待運行時刻再肯定,客戶端只需將請求提交到鏈上,而無須關心請求的處理對象是誰以及它是如何處理的
  • 在不明確指定接收者的狀況下,向多個對象中的一個提交一個請求
  • 可動態指定一組對象處理請求,客戶端能夠動態建立職責鏈來處理請求,還能夠改變鏈中處理者之間的前後次序

相關文章
相關標籤/搜索