設計模式,行爲模式之責任鏈模式

1 概述

責任鏈模式(Chain of Responsibility Pattern)是常見的行爲模式,它將處理器封裝成一條處理鏈,讓請求可以在鏈上傳遞。鏈上的處理器自行決定是否對請求進行處理。

2 責任鏈模式

一個典型的責任鏈模式的使用場景是,當一個事件或請求需要被多個處理器處理時。應用責任鏈模式,將所有的處理器串在一起,然後把請求從鏈的頭部開始傳送。各個處理器可以對請求進行判斷,選擇執行相關邏輯,或者將它傳遞給下一個處理器。如此一來,解耦了請求者和接收者(所有的處理器)。
一般情況下,鏈上的處理器需要持續有下一個處理器的引用,以便將請求往後傳送,除此之外,單個處理器不需要知道整體的結構,只需專注於自己內部的處理邏輯。處理器的增刪改也非常方便,只需在鏈上進行相應處理即可。
example

3 案例

現實中,鏈式處理的案例很多,比如面試流程。一個候選人,需要經過多輪的面試,合格之後,才能入職新公司。這裏的面試官,就是處理器,用來處理面試請求。同時,一個公司有多個領域的面試官,Java面試官,只會選擇面試Java候選人;而HR則會面試所有候選人:

public enum Major {
    JAVA, PYTHON, NA
}
// 面試候選人
public class Interviewee {
    private String name;
    private Major major;
    public Interviewee(String name, Major major) {
        this.name = name;
        this.major = major;
    }
    public String getName() { return name; }
    public Major getMajor() { return major; }
    @Override
    public String toString() {
        return "Interviewee " + name + ", majors in " + major;
    }
}

// 面試官,子類需要實現自己的面試方法
public abstract class Interviewer {
    private Major major;
    protected Interviewer nextInterviewer;
    public Interviewer(Major major) {
        this.major = major;
    }
    public Major getMajor() { return major; }
    // 重要方法:設置下一個面試官,形成責任鏈
    public void setNext(Interviewer nextInterviewer) {
        this.nextInterviewer = nextInterviewer;
    };
    public abstract boolean interview(Interviewee interviewee);
}
// java面試官
public class JavaInterviewer extends Interviewer {
    public JavaInterviewer() {
        super(Major.JAVA);
    }
    @Override
    public boolean interview(Interviewee interviewee) {
        if (interviewee.getMajor().equals(this.getMajor())) {
            // 當領域對應的時候,才進行面試
            System.out.println(interviewee.getName() + " is on java interview.");
            if (new Random().nextInt(10) <= 8) {// 模擬面試結果
                System.out.println(interviewee.getName() + " passed java interview!");
            }
            else {
                System.out.println(interviewee.getName() + " failed on java interview!");
                return false;
            }
        }
        else {
            // 如果領域不對應,直接傳給下一個面試官
            System.out.println("Java interviewer will not do interview on " + interviewee.getName());
        }
        if (nextInterviewer != null) {
            // 傳給下一個面試官
            return nextInterviewer.interview(interviewee);
        }
        return true;
    }
}
// python面試官
public class PythonInterviewer extends Interviewer {
    public PythonInterviewer() {
        super(Major.PYTHON);
    }
    @Override
    public boolean interview(Interviewee interviewee) {
        if (interviewee.getMajor().equals(this.getMajor())) {
            // 當領域對應的時候,才進行面試
            System.out.println(interviewee.getName() + " is on python interview.");
            if (new Random().nextInt(10) <= 8) {// 模擬面試結果
                System.out.println(interviewee.getName() + " passed python interview!");
            }
            else {
                System.out.println(interviewee.getName() + " failed on python interview!");
                return false;
            }
        }
        else {
            // 如果領域不對應,直接傳給下一個面試官
            System.out.println("Python interviewer will not do interview on " + interviewee.getName());
        }
        if (nextInterviewer != null) {
            // 傳給下一個面試官
            return nextInterviewer.interview(interviewee);
        }
        return true;
    }
}
// HR面試官
public class HRInterviewer extends Interviewer {
    public HRInterviewer() {
        super(null);// no major for HR
    }
    @Override
    public boolean interview(Interviewee interviewee) {
        System.out.println(interviewee.getName() + " is on HR interview.");
        if (new Random().nextInt(10) <= 9) {// 模擬面試結果
            System.out.println(interviewee.getName() + " passed hr interview!");
        }
        else {
            System.out.println(interviewee.getName() + " failed on hr interview!");
            return false;
        }
        if (nextInterviewer != null) {
            // 傳給下一個面試官
            return nextInterviewer.interview(interviewee);
        }
        return true;
    }
}
// 責任鏈對象
public class InterviewProcess {
    // 只需只有第一個面試官的引用,請求會自動往後傳
    Interviewer firstInterviewer;
    public InterviewProcess() {
        // 設置責任鏈
        Interviewer javaInterviewer = new JavaInterviewer();
        Interviewer pythonInterviewer = new PythonInterviewer();
        Interviewer hrInterviewer = new HRInterviewer();
        javaInterviewer.setNext(pythonInterviewer);
        pythonInterviewer.setNext(hrInterviewer);
        this.firstInterviewer = javaInterviewer;
    }

    public boolean process(Interviewee interviewee) {
        return firstInterviewer.interview(interviewee);
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        InterviewProcess interviewProcess = new InterviewProcess();
        Interviewee mario = new Interviewee("Mario", Major.JAVA);
        Interviewee link = new Interviewee("Link", Major.PYTHON);

        System.out.println(mario + " is taking interview...");
        boolean result = interviewProcess.process(mario);
        System.out.println("Interview result of " + mario.getName() + ": " + (result ? "pass" : "fail"));
        System.out.println("==================================");
        System.out.println(link + " is taking interview...");
        result = interviewProcess.process(link);
        System.out.println("Interview result of " + mario.getName() + ": " + (result ? "pass" : "fail"));
    }
}

輸出:

Interviewee Mario, majors in JAVA is taking interview...
Mario is on java interview.
Mario failed on java interview!
Interview result of Mario: fail
==================================
Interviewee Link, majors in PYTHON is taking interview...
Java interviewer will not do interview on Link
Link is on python interview.
Link passed python interview!
Link is on HR interview.
Link passed hr interview!
Interview result of Mario: pass

公司的面試官(Interviewer)可以自行註冊到面試流程(InterviewProcess)中來,當有候選人(Interviewee)來參加面試的時候,各面試官可以根據候選人的專業是否匹配,來選擇是否對候選人進行評估。在這個過程中,當有Interviewer的變動(增刪改),我們只需在InterviewProcess進行相應的改動即可,無需影響其他模塊,將變與不變分開。

責任鏈模式在許多著名的框架中都有實現:

  1. Java Servlet中的FilterChainFilter,逐個對請求進行處理。
  2. Netty中的ChannelPipelineChannelHandler,處理EventLoop中的事件。
  3. MyBatis中的Plugin,在SQL執行時鏈式的做一些操作。
    …雖然其中具體的實現方式不一定完全如文中舉的例子,但核心思想都是一樣的:將處理器(Handler)封裝成鏈(Handler Chain),然後將請求沿着鏈逐個傳遞。

4 總結

當請求需要被多個處理器處理時,可以考慮使用責任鏈模式來解耦請求者與處理器。將處理器首尾相接形成一條鏈,鏈中的對象不需要知道鏈的結構,請求對象也只需要自己會被處理即可。

文中例子的github地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章