封面来源:碧蓝航线 破晓冰华 活动CG

本文参考:

Java 设计模式学习网站:Java设计模式:23种设计模式全面解析(超级详细)

菜鸟教程:责任链模式

1. 模式的定义与特点

先看几个场景:

场景一:话说在北朝时期啊,木兰替父从军,但入伍之前得备好装备。先是来到集市东边购买物品,但集市东边只有骏马,没有其他东西,于是又前往集市西边,但这西边只有鞍鞯,没有其他东西,然后又前往集市南边买辔头、前往集市北边买长边,最后便有了“东市买骏马,西市买鞍鞯,南市买辔头,北市买长鞭”(别急,我知道是互文,咱只是举个例子嘛)。

场景二:随着一个“Victory”的字样出现在面前的显示器上,你松开键盘和鼠标,伸了个懒腰,定睛一看,已经凌晨一点半了,一股饥饿感也涌上了脑袋。“看来得搞点吃的啊,点个外卖吧”,打开应用、选择物品、付款一气呵成,等待外卖的到来。不一会一个电话打了过来,你以为是外卖到了,高兴地拿起手机,只听见话筒另一边传来一个声音“先生,不好意思,您所购买的商品已经售罄了,您能取消订单选择其他商品或前往分店 B 购买相同商品吗?”,而你就是倔强,今晚必须吃这些。说换就换,你取消订单来到了分店 B,结果发现分店 B 关门了,电话询问后又来到了分店 C 下单,但又发现分店 C 不在配送范围。一顿操作后,你终于在分店 G 成功下单,等到最终拿到外卖,已经凌晨三点了。

场景三:工作了大半年,你想请个假陪陪家人。你得先向你的直接上级请假,然后还得向你所在模块的老板请假,让他们知道你请假的情况,最后还得向人事请假,扣除相应的工资,但这样东跑一下,西跑一下十分不方便,有没有什么办法可以解决这个问题呢?

场景四:周一的下午你正在工位上愉快地摸鱼,这时候老板走过来给你说:“小陈啊,我们公司的系统得对接第三方系统,我看你平时任务完成速度与质量都挺好,这一块就由你来负责吧”。你下定决心不能辜负老板的期待,得把第三方系统的对接搞好,但现在问题也来了,不同第三方系统的对接逻辑很有可能不一样,并且同一个系统随着时间的推移对接逻辑也可能发生变化,这可咋搞呢?聪明的你一下子想到了使用 if-else 解决,但是谁知道对接的系统后续会不会增加,增加之后虽然可以继续添加判断语句,但会让系统的后续维护难以进行,有没有什么方法既能够解决这个问题又能让系统拥有良好的维护性、拓展性呢?

在场景一中,木兰如果有一份购物清单,上面列举了各种物品的购买地点,相信一定能够提升不少效率;在场景二中,如果外卖平台能够自动根据所选店家的配送情况选择符合要求的分店进行配送,咱也不会饿半天的肚子了;而在场景三中,如果有一个 OA 系统能够将请假的步骤以协同表单的形式进行,一定能节约不少时间;最后在场景四中已经明确了需求,唯一要做的就是寻找到解决问题的方法。

针对上述这些场景,使用设计模式中的【责任链模式】就十分契合。

定义

责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

简单来说,【责任链模式】为请求创建了一个处理者的链,请求会沿着这条链传递,当请求满足某个具体的处理者时,请求不再沿着链移动,请求由这个处理者处理。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

责任链模式也叫职责链模式。显然,责任链模式是一种对象行为型模式。

在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。

优点

1、降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。

2、增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。

3、增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。

4、责任链简化了对象之间的连接,使得对象无需知道链的结构。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。

5、责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

缺点

1、不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。

2、对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。

3、职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

4、不容易观察运行时特征,增加系统的复杂度,不便于错误排查。

2. 适用场景

1、多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。

2、可动态指定一组对象处理请求,或添加新的处理者。

3、在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。

3. 模式的结构

通过对前文的了解,不难发现可以使用 链表 来实现责任链模式的数据结构。

【责任链模式】的主要角色如下:

1、抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。

2、具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。

3、客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。

【责任链模式】结构图:

责任链模式的结构图

客户端设置的责任链:

责任链模式中客户端设置的责任链

4. 代码的实现

结构图已有,代码实现还难吗?

首先编写抽象处理者的类,使用链表来实现责任链模式中的数据结构:

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
/**
* 抽象处理者
*
* @author mofan
* @date 2021/8/22 19:03
*/
public abstract class Handler {
/**
* 链表的下一个对象
*/
private Handler next;

public void setNext(Handler next) {
this.next = next;
}

public Handler getNext() {
return next;
}

/**
* 处理请求的方法
* @param request 请求信息
* @return 处理结果
*/
public abstract String handleRequest(String request);
}

针对抽象处理者,派生出两个具体处理者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 具体处理者 1 号
*
* @author mofan
* @date 2021/8/22 19:05
*/
public class ConcreteHandlerOne extends Handler {
public static final String ONE = "one";
@Override
public String handleRequest(String request) {
if (ONE.equals(request)) {
return ONE;
} else {
if (this.getNext() != null) {
return this.getNext().handleRequest(request);
} else {
throw new UnsupportedOperationException();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 具体处理者 2 号
*
* @author mofan
* @date 2021/8/22 19:11
*/
public class ConcreteHandlerTwo extends Handler {
public static final String TWO = "two";
@Override
public String handleRequest(String request) {
if (TWO.equals(request)) {
return TWO;
} else {
if (this.getNext() != null) {
return this.getNext().handleRequest(request);
} else {
throw new UnsupportedOperationException();
}
}
}
}

接下组装处理者并进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @author mofan
* @date 2021/8/22 19:32
*/
public class ChainTest {
@Test
public void testChainOfResponsibilityPattern() {
Handler handlerOne = new ConcreteHandlerOne();
Handler handlerTwo = new ConcreteHandlerTwo();
// 组装责任链
handlerOne.setNext(handlerTwo);
// 提交请求
String response = handlerOne.handleRequest("one");
Assert.assertEquals("one", response);
response = handlerOne.handleRequest("two");
Assert.assertEquals("two", response);
try {
handlerOne.handleRequest("three");
Assert.fail();
} catch (Exception e) {
Assert.assertTrue(e instanceof UnsupportedOperationException);
}
}
}

运行上述测试类后,测试通过。

在上面代码中,我们把请求消息硬编码为 String 类型,而在真实业务中,消息一般是自定义类型。因此,我们一般对请求消息类型进行抽象。比方说,编写一个请求消息接口或者抽象请求消息类 Request,让具体的请求消息都实现这个接口或抽象类,而抽象处理者中处理请求的抽象方法 handleRequest() 就可以写成:

1
public abstract Reponse handleRequest(Request request);

5. 模式的拓展

责任链模式存在以下两种情况:

1、纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。

2、不纯的职责链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。

关于责任链模式的具体应用,在 Tomcat 的 Filter 中、SpringSecurity 的过滤器链都使用到了【责任链模式】。