行为模式之责任链模式

Posted by Night Field's Blog on June 6, 2020

1 概述

责任链模式(Chain of Responsibility Pattern)是常见的行为模式,它将处理器封装成一条处理链,让请求可以在链上传递。链上的处理器自行决定是否对请求进行处理。

2 责任链模式

一个典型的责任链模式的使用场景是,当一个事件或请求需要被多个处理器处理时。应用责任链模式,将所有的处理器串在一起,然后把请求从链的头部开始传送。各个处理器可以对请求进行判断,选择执行相关逻辑,或者将它传递给下一个处理器。如此一来,解耦了请求者和接收者(所有的处理器)。 一般情况下,链上的处理器需要持续有下一个处理器的引用,以便将请求往后传送,除此之外,单个处理器不需要知道整体的结构,只需专注于自己内部的处理逻辑。处理器的增删改也非常方便,只需在链上进行相应处理即可。 example

3 案例

现实中,链式处理的案例很多,比如面试流程。一个候选人,需要经过多轮的面试,合格之后,才能入职新公司。这里的面试官,就是处理器,用来处理面试请求。同时,一个公司有多个领域的面试官,Java面试官,只会选择面试Java候选人;而HR则会面试所有候选人:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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"));
    }
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
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地址