责任链模式介绍

责任链模式的核心是解决一组服务中的先后执行关系,就像出差借款需要审批,5000元以下直接找部门领导、分管领导、财务部门审批,5000元以上需要找更高一级的领导审批。

责任链模式的简单使用

使用场景:审批场景

在审批的过程中,在特定时间点会加入不同级别的负责人,每位负责人就像责任链模式中的一个核心点。研发人员并不需要关心具体的审批流程处理细节,只需要知道审批上线更严格、级别也更高。

业务场景:

image-20211118221804362

模拟审批类服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.bestrookie.design;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author bestrookie
* @date 2021/11/18 10:22 上午
*/
public class AuthService {
private static Map<String, Date> authMap = new ConcurrentHashMap<>();
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值,记录在内存结构中。

认证信息类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.bestrookie.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author bestrookie
* @date 2021/11/18 10:12 上午
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthInfo {
private String code;
private String info = "";
public AuthInfo(String code,String ...infos){
this.code = code;
for (String info : infos) {
this.info = this.info.concat(info);
}
}
}

违背设计模式实现

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
package com.bestrookie.design;
import com.bestrookie.pojo.AuthInfo;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author bestrookie
* @date 2021/11/18 10:28 上午
*/
public class AuthController {
private SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public AuthInfo doAuth(String uId, String orderId, Date authDate) throws ParseException {
Date date = AuthService.queryAuthInfo("1000013",orderId);
if (null == date){
return new AuthInfo("0001","单号: ",orderId,"状态:待三级负责人审批 ","王工");
}
if (authDate.after(f.parse("2020-06-11 00:00:00")) && authDate.before(f.parse("2020-06-25 23:59:59"))){
date = AuthService.queryAuthInfo("1000012",orderId);
if (null == date){
return new AuthInfo("0001","单号: ",orderId,"状态:待二级负责人审批 ","张经理");
}
}
if (authDate.after(f.parse("2020-06-11 00:00:00")) && authDate.before(f.parse("2020-06-20 23:59:59"))){
date = AuthService.queryAuthInfo("1000011",orderId);
if (null == date){
return new AuthInfo("0001","单号: ",orderId,"状态:待一级负责人审批 ","段总");
}
}
return new AuthInfo("0001","单号: ",orderId,"状态:审核完成");
}
}

这段代码从上到下分别对在指定范围内由不同的人员审批进行了判断,就像大促期间上线需要三位负责人都审批才允许系统上线一样。这种功能看起来很简单,但在实际的业务中会有很多部门逻辑。如果按这样实现就很难进行扩展,并且改动扩展时也非常麻烦。

责任链模式重构代码

责任链模式可以让各个服务模块更加清晰,而每一个模块间通过next的方式获取。而每一next是由继承的统一抽象类实现的。最终,所有类的职责可以动态地编排使用,编排的过程可以做成可配置化的。

image-20211119104906509

通过实现统一抽象类AuthLink的三种编排责任,模拟出一条链路。这条链路就是业务中的责任链。

在使用责任链时,如果场景比较固定,可以通过写死到代码中进行初始化;如果业务场景经常变化,可以做成XML配置的方式处理,也可以存放到数据库里初始化。

链路抽象类定义
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
package com.bestrookie.design.impl;
import com.bestrookie.pojo.AuthInfo;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author bestrookie
* @date 2021/11/18 1:57 下午
*/
public abstract class AuthLink {
protected SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
protected String levelUserId;
protected String levelUserName;
protected 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的方式获取下一个链路需要处理的节点。levelIserId、levelUserName是责任链中的公用信息,标记每一个审批节点的人员信息。抽象类中定义了一个抽象方法abstract AuthInfo doAuth,是每一实现着必须实现的类,不同的审批级别人员处理不同的业务。

三个审批实现类

LevelOneAuthLink:

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
package com.bestrookie.design.impl;
import com.bestrookie.design.AuthService;
import com.bestrookie.pojo.AuthInfo;
import java.text.ParseException;
import java.util.Date;
/**
* @author bestrookie
* @date 2021/11/18 2:24 下午
*/
public class LevelOneAuthLink 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 LevelOneAuthLink(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(date == null){
return new AuthInfo("0001","单号:",orderId," 状态:待一级负责人审批 ",levelUserName);
}
AuthLink next = super.next();
if (next == null){
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);
}
}

LevelTwoAuthLink:

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
package com.bestrookie.design.impl;
import com.bestrookie.design.AuthService;
import com.bestrookie.pojo.AuthInfo;
import java.text.ParseException;
import java.util.Date;
/**
* @author bestrookie
* @date 2021/11/18 2:59 下午
*/
public class LevelTwoAuthLink 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 LevelTwoAuthLink(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 (date == null){
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);
}
}

LevelThreeLink:

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
package com.bestrookie.design.impl;
import com.bestrookie.design.AuthService;
import com.bestrookie.pojo.AuthInfo;
import java.util.Date;
/**
* @author bestrookie
* @date 2021/11/18 3:29 下午
*/
public class LevelThreeLink extends AuthLink{
public LevelThreeLink(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);
}
}

上面三个类实现了不同的审批级别处理的简单逻辑。例如,第一个审批类辉县判断是否审批已通过,如果没有通过,则将结果返回给调用方,引导去审批。判断完成后获取下一个审批节点super.next(),如果不存在下一个节点,则直接返回结果。之后根据不同的业务时间段判断是否需要二级负责人审批和一级负责人审批。最后返回下一个审批结果next.doAuth(uId,orderId,authDate);,就像递归调用。

总结:

通过使用if语句到使用责任链模式,代码结构变得更加清晰明了,也解决了大量if语句的调用问题。当然,并不if语句不好,只不过if语句并不适合做系统流程设计,在处理判断和行为逻辑中还是可以使用的。组合模式想一颗树,而这里搭建出一棵流程决策树。这种模式可也以和责任链模式组合扩展使用,这部分的重点在于如何关联链路,最终都是在执行中间的关系链。

责任链模式可以很好地运用单一职责原则和开闭原则,既降低了耦合,也使对象关系更加清晰,并且外部调用方并不需要关心责任链是如何处理的。

评论