责任链 Chain of Responsibility

责任链模式是一种行为设计模式,允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者。简单来说就是通过构建一个处理流水线来对一次请求进行多次的处理。


为什么要使用?

责任链模式的对象职责:

通过构建一个处理流水线来对一次请求进行多次的处理,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者。

👉 解耦使用者和后台庞大的流程化处理。

👉 为了动态更换流程处理中的处理对象。

👉 为了处理一些需要递归遍历的对象列表。

🎈 责任链模式弱化了发出请求的人和处理请求的人之间的关系,提高了组件的复用性,让每个处理者专注于自己的工作。通过动态地重组责任链也能够让程序变得更加灵活。


模式结构

  • 处理者(Handler)声明了所有具体处理者的通用接口。该接口通常仅包含单个方法用于请求处理,但有时其还会包含一个设置链上下个处理者的方法。
  • 基础处理者(Base Handler)是一个可选的类,你可以将所有处理者共用的样本代码放置在其中。通常情况下,该类中定义了一个保存对于下个处理者引用的成员变量。客户端可通过将处理者传递给上个处理者的构造函数或设定方法来创建链。该类还可以实现默认的处理行为:确定下个处理者存在后再将请求传递给它。
  • 具体处理者(Concrete Handlers)包含处理请求的实际代码。每个处理者接收到请求后,都必须决定是否进行处理,以及是否沿着链传递请求。处理者通常是独立且不可变的,需要通过构造函数一次性地获得所有必要地数据。
  • 客户端(Client)可根据程序逻辑一次性或者动态地生成链。值得注意的是,请求可发送给链上的任意一个处理者,而非必须是第一个处理者。

责任链模式的类图:


模式实现

该示例使用责任链模式实现了处理者根据问题编号处理问题。具体类信息如下:

责任链顺序 类名 职责
1 NoSupport 永远不解决问题
2 LimitSupport 只解决编号小于 limit 值的问题
3 SpecialSupport 只解决指定编号的问题
4 OddSupport 只解决奇数编号的问题

通过处理者的 setNext 方法设置责任链,使用其 support 方法解决问题。当前处理者能够解决问题时,优先当前处理者解决;如果无法解决则交由下一位处理者解决;如果没有下一位处理者则说明问题无法解决。

示例程序的类图

代码实现

基础处理者
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
package example;

/** 基础处理者 */
public abstract class Support {
/** 处理者名称 */
private String name;
/** 下一个处理者 */
private Support next;

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

/**
* 设置下一个处理者,链式调用
* @param next 下一个处理者
*/
public Support setNext(Support next) {
this.next = next;
return next;
}

/**
* 解决问题步骤
* 1. 自身可以解决
* 2. 存在下一个处理者,交给下一个处理者解决
* 3. 不存在下一个处理者,无法解决
* @param trouble 问题
*/
public final void support(Trouble trouble) {
if (resolve(trouble)) {
done(trouble);
} else if (next != null) {
next.support(trouble);
} else {
fail(trouble);
}
}

/**
* 成功解决
* @param trouble 问题
*/
protected void done(Trouble trouble) {
System.out.println("[" + trouble.getNumber() + "]问题已解决!处理人:{" + name + "}");
}

/**
* 无法解决
* @param trouble 问题
*/
protected void fail(Trouble trouble) {
System.out.println("[" + trouble.getNumber() + "]问题未解决!");
}

/**
* 能否解决问题,在解决问题后访问是否成功解决
* @param trouble 问题
* @return true可以解决
*/
protected abstract boolean resolve(Trouble trouble);
}
具体处理者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package example;

/** 永远不解决问题的处理者 */
public class NoSupport extends Support {
public NoSupport(String name) {
super(name);
}

/**
* 无法解决任何问题
* @param trouble 问题
* @return 永远返回false
*/
@Override
protected boolean resolve(Trouble trouble) {
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package example;

/** 只解决编号小于limit值问题的处理者 */
public class LimitSupport extends Support {
private int limit;

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

/**
* 只解决编号小于limit值的问题
* @param trouble 问题
* @return true可以解决
*/
@Override
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() < limit) {
return true;
}
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package example;

/** 只解决指定编号问题的处理者 */
public class SpecialSupport extends Support {
private int number;

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

/**
* 只解决指定编号的问题
* @param trouble 问题
* @return true可以解决
*/
@Override
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() == number) {
return true;
}
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package example;

/** 只解决奇数编号问题的处理者 */
public class OddSupport extends Support {
public OddSupport(String name) {
super(name);
}

/**
* 只解决奇数编号的问题
* @param trouble 问题
* @return true可以解决
*/
@Override
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() % 2 == 1) {
return true;
}
return false;
}
}
其他类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package example;

/** 其他类,表示发生的问题 */
public class Trouble {
/** 问题编号 */
private int number;

public Trouble(int number) {
this.number = number;
}

public int getNumber() {
return number;
}
}

代码测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import example.*;

/** 测试责任链模式 */
public class Test {
public static void main(String[] args) {
Support zhangsan = new NoSupport("张三");
Support lisi = new LimitSupport("李四(只会解决编号小于20的问题)", 20);
Support wangwu = new SpecialSupport("王五(只会解决编号为20的问题)", 20);
Support zhaoliu = new OddSupport("赵六(只会解决奇数编号的问题)");
// 配置责任链
zhangsan.setNext(lisi).setNext(wangwu).setNext(zhaoliu);
System.out.println("责任链的顺序为:张三{NoSupport} --> 李四{LimitSupport} --> 王五{SpecialSupport} --> 赵六{OddSupport}");

zhangsan.support(new Trouble(15));
zhangsan.support(new Trouble(20));
zhangsan.support(new Trouble(21));
zhangsan.support(new Trouble(22));
}
}

输出结果

1
2
3
4
5
责任链的顺序为:张三{NoSupport} --> 李四{LimitSupport} --> 王五{SpecialSupport} --> 赵六{OddSupport}
[15]问题已解决!处理人:{李四(只会解决编号小于20的问题)}
[20]问题已解决!处理人:{王五(只会解决编号为20的问题)}
[21]问题已解决!处理人:{赵六(只会解决奇数编号的问题)}
[22]问题未解决!

常用场景和解决方案

  • 在运行时需要动态使用多个关联对象来处理同一次请求时,如请假流程。
  • 不想让使用者知道具体的处理逻辑时,如权限校验的登录拦截器。
  • 需要动态更换处理对象或处理顺序时。在处理者类中有对引用成员变量的设定方法,你将能动态地插入和移除处理者,或者改变其顺序。
  • 当程序需要使用不同方式处理不同种类请求,而且请求类型和顺序预先未知时。该模式能将多个处理者连接成一条链。接收到请求后,它会“询问”每个处理者是否能够对其进行处理。这样所有处理者都有机会来处理请求。

模式的优缺点

优点 缺点
你可以控制请求处理的顺序。 部分请求可能未被处理。
单一职责原则。你可对发起操作和执行操作的类进行解耦。
开闭原则。你可以在不更改现有代码的情况下在程序中新增处理者。

使用责任链模式的优势

  • 降低客户端对象与处理链条上对象之间的耦合度。
  • 提升系统扩展性。
  • 增强了具体处理类的职责独立性。
  • 简化了对象之间前后关联处理的复杂性。

使用责任链模式的劣势

  • 降低性能。
  • 调试难度增大。
  • 容易出现死锁异常,需要注意维护上下文关系的正确性。

拓展知识

  • 由于链的动态性,客户端需要准备好处理以下情况:

    • 链中可能只有单个链接。
    • 部分请求可能无法到达链尾。
    • 其他请求可能直到链尾都未被处理。
  • 责任链和装饰模式的类结构非常相似。两者都依赖递归组合将需要执行的操作传递给一系列对象。但是,两者有几点重要的不同之处。

    • 在责任链模式中,责任链的管理者可以相互独立地执行一切操作,还可以随时停止传递请求。
    • 在装饰器模式中,各种装饰可以在遵循基本接口的情况下扩展对象的行为。此外,装饰无法中断请求的传递。
  • 责任链通常和组合模式结合使用。在这种情况下,叶组件接收到请求后,可以将请求沿包含全体父组件的链一直传递至对象树的底部。

  • 虽然责任链模式能让程序更加灵活但也会导致处理延迟。在面对需要非常快的处理速度时,责任链模式显然不是个好选择。



🔙 设计模式

📌最后:希望本文能够给您提供帮助,文章中有不懂或不正确的地方,请在下方评论区💬留言!

🔗参考文献:

🌐 设计模式 –refactoringguru

▶️ bilibili-趣学设计模式;黄靖锋. –拉勾教育

📖 图解设计模式 /(日)结城浩著;杨文轩译. –北京:人民邮电出版社,2017.1