责任链模式
# 定义
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
责任链模式的核心是解决一组服务中的先后执行处理关系,就有点像你没钱花了,需要家庭财务支出审批,10块钱以下找闺女审批,100块钱先闺女审批在媳妇审批。你可以理解想象成当你要跳槽的时候被安排的明明白白的被各个领导签字放行。
优点:
1. 降低耦合度。它将请求的发送者和接收者解耦。
2. 简化了对象。使得对象不需要知道链的结构。
3. 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
4. 增加新的请求处理类很方便。
缺点:
1. 不能保证请求一定被接收。
2. 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
3. 可能不容易观察运行时的特征,有碍于除错。
使用场景:
1. 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
2. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
3. 可动态指定一组对象处理请求。
# 实践
本次模拟在 618 大促期间的业务系统上线审批流程场景。
>像是这些一线电商类的互联网公司,阿里、京东、拼多多等,在 618 期间都会做一些运营活动场景以及提供的扩容备战,就像过年期间百度的红包一样。但是所有开发的这些系统都需要陆续的上线,因为临近 618 有时候也有一些紧急的调整的需要上线,但为了保障线上系统的稳定性是尽可能的减少上线的,也会相应的增强审批力度。就像一级响应、二级响应一样。
>
>而这审批的过程在随着特定时间点会增加不同级别的负责人加入,每个人就像责任链模式中的每一个核心点。对于研发小伙伴并不需要关心具体的审批流程处理细节,只需要知道这个上线更严格,级别也更高,但对于研发人员来说同样是点击相同的提审按钮,等待审核。
接下来我们就模拟这样一个业务诉求场景,使用责任链的设计模式来实现此功能。
责任链模式可以让各个服务模块更加清晰,而每一个模块间可以通过 next 的方式进行获取。而每一个 next 是由继承的统一抽象类实现的。最终所有类的职责可以动态的进行编排使用,编排的过程可以做成可配置化。
代码结构

模拟审核服务
```java
public class AuthService {
private static Map<String, Date> authMap = new ConcurrentHashMap<String, Date>();
public static Date queryAuthInfo(String uId, String orderId) {
return authMap.get(uId.concat(orderId));
}
public static void auth(String uId, String orderId) {
authMap.put(uId.concat(orderId), new Date());
}
}
```
- 这里面提供了两个接口一个是查询审核结果(queryAuthInfo)、另外一个是处理审核(auth)。
- 这部分是把由谁审核的和审核的单子 ID 作为唯一 key 值记录到内存 Map 结构中。

- 上图是这个业务模型中责任链结构的核心部分,通过三个实现了统一抽象类 AuthLink 的不同规则,再进行责任编排模拟出一条链路。这个链路就是业务中的责任链。
- 一般在使用责任链时候如果是场景比较固定,可以通过写死到代码中进行初始化。但如果业务场景经常变化可以做成 xml 配置的方式进行处理,也可以落到库里进行初始化操作。
责任链中返回对象定义
```java
@Data
public class AuthInfo {
private String code;
private String info = "";
public AuthInfo(String code, String ...infos) {
this.code = code;
for (String str:infos){
this.info = this.info.concat(str);
}
}
}
```
链路抽象类定义
```java
public abstract class AuthLink {
// 时间格式化
protected SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 级别人员ID
protected String levelUserId;
// 级别人员姓名
protected String levelUserName;
// 责任链
private AuthLink next;
public AuthLink(String levelUserId, String levelUserName) {
this.levelUserId = levelUserId;
this.levelUserName = levelUserName;
}
public AuthLink next() {
return next;
}
public AuthLink appendNext(AuthLink next) {
this.next = next;
return this;
}
public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);
}
```
- 这部分是责任链,链接起来的核心部分。AuthLink next,重点在于可以通过 next 方式获取下一个链路需要处理的节点。
- levelUserId、levelUserName,是责任链中的公用信息,标记每一个审核节点的人员信息。
- 抽象类中定义了一个抽象方法,abstract AuthInfo doAuth,这是每一个实现者必须实现的类,不同的审核级别处理不同的业务。
三个审核实现类
```java
public class Level1AuthLink extends AuthLink {
public Level1AuthLink(String levelUserId, String levelUserName) {
super(levelUserId, levelUserName);
}
@Override
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
Date date = AuthService.queryAuthInfo(levelUserId, orderId);
if (null == date) {
return new AuthInfo("0001", "单号:", orderId, " 状态:待一级审批负责人 ", levelUserName);
}
AuthLink next = super.next();
if (null == next) {
return new AuthInfo("0000", "单号:", orderId, " 状态:一级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
}
return next.doAuth(uId, orderId, authDate);
}
}
public class Level2AuthLink extends AuthLink {
private Date beginDate = f.parse("2020-06-11 00:00:00");
private Date endDate = f.parse("2020-06-20 23:59:59");
public Level2AuthLink(String levelUserId, String levelUserName) throws ParseException {
super(levelUserId, levelUserName);
}
@Override
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
Date date = AuthService.queryAuthInfo(levelUserId, orderId);
if (null == date) {
return new AuthInfo("0001", "单号:", orderId, " 状态:待二级审批负责人 ", levelUserName);
}
AuthLink next = super.next();
if (null == next) {
return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
}
if (authDate.before(beginDate) || authDate.after(endDate)) {
return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
}
return next.doAuth(uId, orderId, authDate);
}
}
public class Level3AuthLink extends AuthLink {
private Date beginDate = f.parse("2020-06-01 00:00:00");
private Date endDate = f.parse("2020-06-25 23:59:59");
public Level3AuthLink(String levelUserId, String levelUserName) throws ParseException {
super(levelUserId, levelUserName);
}
@Override
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
Date date = AuthService.queryAuthInfo(levelUserId, orderId);
if (null == date) {
return new AuthInfo("0001", "单号:", orderId, " 状态:待三级审批负责人 ", levelUserName);
}
AuthLink next = super.next();
if (null == next) {
return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批负责人完成", " 时间:", f.format(date), " 审批人:", levelUserName);
}
if (authDate.before(beginDate) || authDate.after(endDate)) {
return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批负责人完成", " 时间:", f.format(date), " 审批人:", levelUserName);
}
return next.doAuth(uId, orderId, authDate);
}
}
```
- 如上三个类;Level1AuthLink、Level2AuthLink、Level3AuthLink,实现了不同的审核级别处理的简单逻辑。
- 例如第一个审核类中会先判断是否审核通过,如果没有审核通过则返回结果给调用方,引导去审核。(这里简单模拟审核后有时间信息不为空,作为判断条件)
- 判断完成后获取下一个审核节点;super.next();,如果不存在下一个节点,则直接返回结果。
- 之后是根据不同的业务时间段进行判断是否需要,二级和一级的审核。
最后返回下一个审核结果;next.doAuth(uId, orderId, authDate);,有点像递归调用。
测试类
```java
@Slf4j
public class ApiTest {
@Test
public void test_AuthLink() throws ParseException {
AuthLink authLink = new Level3AuthLink("1000013", "王工")
.appendNext(new Level2AuthLink("1000012", "张经理")
.appendNext(new Level1AuthLink("1000011", "段总")));
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date testDate = f.parse("2020-06-15 00:00:00");
log.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("lixj", "1000998004813441", testDate)));
// // 模拟三级负责人审批
AuthService.auth("1000013", "1000998004813441");
log.info("测试结果:{}", "模拟三级负责人审批,王工");
log.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("1000013", "1000998004813441", testDate)));
//
// 模拟二级负责人审批
AuthService.auth("1000012", "1000998004813441");
log.info("测试结果:{}", "模拟二级负责人审批,张经理");
log.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("1000012", "1000998004813441", testDate)));
//
// 模拟一级负责人审批
AuthService.auth("1000011", "1000998004813441");
log.info("测试结果:{}", "模拟一级负责人审批,段总");
log.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("1000011", "1000998004813441", testDate)));
}
}
```
- 这里包括最核心的责任链创建,实际的业务中会包装到控制层;AuthLink authLink = new Level3AuthLink("1000013", "王工") .appendNext(new Level2AuthLink("1000012", "张经理") .appendNext(new Level1AuthLink("1000011", "段总"))); 通过把不同的责任节点进行组装,构成一条完整业务的责任链。
- 接下里不断的执行查看审核链路authLink.doAuth(...),通过返回结果对数据进行3、2、1级负责人审核,直至最后审核全部完成。
测试结果
```
18:36:14.483 [main] INFO ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待三级审批负责人 王工"}
18:36:14.485 [main] INFO ApiTest - 测试结果:模拟三级负责人审批,王工
18:36:14.485 [main] INFO ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待二级审批负责人 张经理"}
18:36:14.485 [main] INFO ApiTest - 测试结果:模拟二级负责人审批,张经理
18:36:14.485 [main] INFO ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待一级审批负责人 段总"}
18:36:14.485 [main] INFO ApiTest - 测试结果:模拟一级负责人审批,段总
18:36:14.485 [main] INFO ApiTest - 测试结果:{"code":"0000","info":"单号:1000998004813441 状态:一级审批完成负责人 时间:2022-01-29 18:36:14 审批人:段总"}
```
- 从上述的结果可以看到我们的责任链已经生效,按照责任链的结构一层层审批,直至最后输出审批结束到一级完成的结果。
- 这样责任链的设计方式可以方便的进行扩展和维护,也把if语句干掉了。
# 总结
- 从上面代码从 if 语句重构到使用责任链模式开发可以看到,我们的代码结构变得清晰干净了,也解决了大量 if 语句的使用。并不是 if 语句不好,只不过 if 语句并不适合做系统流程设计,但是在做判断和行为逻辑处理中还是非常可以使用的。
- 在我们前面学习结构性模式中讲到过组合模式,它像是一颗组合树一样,我们搭建出一个流程决策树。其实这样的模式也是可以和责任链模型进行组合扩展使用,而这部分的重点在于如何关联链路的关联,最终的执行都是在执行在中间的关系链。
- 责任链模式很好的处理单一职责和开闭原则,简单了耦合也使对象关系更加清晰,而且外部的调用方并不需要关心责任链是如何进行处理的*(以上程序中可以把责任链的组合进行包装,在提供给外部使用)*。但除了这些优点外也需要是适当的场景才进行使用,避免造成性能以及编排混乱调试测试疏漏问题。
>原文GitHub地址:https://bugstack.cn/md/develop/design-pattern/2020-06-18-%E9%87%8D%E5%AD%A6%20Java%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%8A%E5%AE%9E%E6%88%98%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F%E3%80%8B.html#%E4%B8%89%E3%80%81%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F%E4%BB%8B%E7%BB%8D

【设计模式】责任链模式