一、什么是责任链模式
责任链模式是一种设计模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。(摘自百度百科)
像上图这个审核流程,从开始到结束,需要五步,这五步组成了一个链路,这个就可以看作责任链,当然,我们可以通过if语句,依次写,但是,这样写出来的代码,不优雅,且后续想再加流程或修改某一地方,当看到一堆的if-else时,已经不想改了……
二、场景模拟
为了应用责任链模式,我这里模拟一个场景--预约活动场景。首先,预约之前,要检查用户是否有资格预约,或者是这个活动还有没有效,或者……。一堆的检查,看下面这个流程图:
1、活动有效性检查:我们要看此用户预约的活动,是否还在有效期内等等;
2、活动规则检查:看用户是否符合此活动的规则,例如仅限新用户啊,或者限男女等等;
3、剩余容量检查:没剩余容量了,用户就无法预约了。
以上就是我们简单的三个需求,当然后续可以扩增,但这里就不赘述,ctrl c+v而已。
三、代码工程
3.1、工程结构
其中:domian放置一些类信息,例如用户User、活动Maneuver;BookingInfo接收用户发送的预约请求; CheckInfo模拟校验链返回信息;枚举类CheckStatus时一些状态枚举信息。基于以上,我们简单实现以下。
3.2、基础代码
User.class(简单模拟用户)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
// 用户
private String name;
// 其他信息
private String otherInfo;
}
Maneuver.class(简单模拟活动)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Maneuver {
// 名称
private String name;
// 开始
private LocalDateTime startTime;
// 结束
private LocalDateTime endTime;
// 容量
private Long capacity;
// 其它规则
private String rule;
}
BookingInfo.class(简单模拟接收用户发送的预约请求)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BookingInfo {
// 预约用户名
private String userName;
// 预约活动名
private String maneuverName;
// 其它信息
private String otherInfo;
}
CheckStatus.class(简单模拟状态枚举信息)
public enum CheckStatus {
SUCCESS("10001"),
ERROR("20001"),
EXPIRED("30001"),
NO_MANEUVER("40001"),
NO_MANEUVER_SEGMENT("40002"),
VOLUME_LESS("50001");
private final String code;
CheckStatus(String code){
this.code = code;
}
public String getCode(){
return this.code;
}
}
CheckInfo.class(简单模拟校验返回信息)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CheckInfo {
// 状态
private CheckStatus checkStatus;
// 描述信息
private String info;
}
以上,基础代码部分就结束了,接下来,我们来开始模拟业务。
四、业务实现
4.1、使用if-else方式实现
public static void errorDemo(Maneuver maneuver, User user, BookingInfo bookingInfo){
LocalDateTime now = LocalDateTime.now();
// 1、检验此活动是否有效;
if (!now.isBefore(maneuver.getEndTime())) {
// ...还有其它校验
log.error("活动过期!");
return;
}
// 2、其它关键规则校验;
if (!user.getOtherInfo().equals(maneuver.getRule())) {
// ...还有其它校验
log.error("不符合规则!");
return;
}
// 3、检验此活动是否有剩余容量;
if (maneuver.getCapacity() <= 0) {
// ...还有其它校验
log.error("剩余容量不足");
return;
}
log.info("预约成功!" + bookingInfo);
}
然后我们写个测试类,实现一下:
public static void main(String[] args) {
// 模拟活动
Maneuver maneuver = new Maneuver("预约活动1",
LocalDateTime.of(2023, 11, 19, 7, 0),
LocalDateTime.of(2023, 11, 19, 19, 0),
1000L,
"man");
// 模拟用户
User user = new User("张三", "man");
// 模拟预约信息
BookingInfo bookingInfo = new BookingInfo("张三", "预约活动1", "...");
// 使用if来判断
errorDemo(maneuver, user, bookingInfo);
}
运行结果:
然后我们修改一下User信息模拟一下失败的场景:
可以看到,确实是能实现功能。但是,我这里if代码块里的功能只是做了简化的描述,真实的业务场景校验规则可能有很多很多的代码逻辑,就会导致一堆一堆的if-else堆积在这里,看着就很恶心。所以,我们要用责任链设计模式来优化它!
4.2、使用责任链设计模式重构
4.2.1、责任链模式结构
首先我们创建一个CheckLink抽象类,新建类去继承CheckLink,实现他的doCheck()方法,这样可以灵活新建校验;且配合next和实现下灵活搭配。
4.2.2、创建责任链抽象类
public abstract class CheckLink {
private String checkLinkName; // 此校验链名称
private CheckLink next; // 下一个校验链
public CheckLink(String checkLinkName) {
this.checkLinkName = checkLinkName;
}
public CheckLink setNext(CheckLink next){
this.next = next;
return this;
}
public CheckLink getNext(){
return this.next;
}
public String getCheckLinkName(){
return this.checkLinkName;
}
public abstract CheckInfo doCheck(User user, Maneuver maneuver, BookingInfo bookingInfo);
}
上面重要的信息只有一个,就是next变量,这个变量指向了下一个责任链。如果下一个责任链为空,说明这就是责任链的最后一个了;如果下一个责任链不为空,那就依次往后执行。
4.2.3、实现类模拟检验规则
首先我们模拟活动校验
@Slf4j
public class ManeuverCheck extends CheckLink {
public ManeuverCheck(String checkLinkName) {
super(checkLinkName);
}
@Override
public CheckInfo doCheck(User user, Maneuver maneuver, BookingInfo bookingInfo) {
log.info(getCheckLinkName() + " start ...");
LocalDateTime now = LocalDateTime.now();
// 1、检验此活动是否有效;
if (!now.isBefore(maneuver.getEndTime())) {
return new CheckInfo(CheckStatus.EXPIRED, "活动已过期");
}
// 如果后面没有了,说明校验通过
if(getNext() == null){
return new CheckInfo(CheckStatus.SUCCESS, "校验通过");
}
// 如果后面还有校验链,再继续执行
return getNext().doCheck(user, maneuver, bookingInfo);
}
}
然后模拟规则校验
@Slf4j
public class RuleCheck extends CheckLink {
public RuleCheck(String checkLinkName) {
super(checkLinkName);
}
@Override
public CheckInfo doCheck(User user, Maneuver maneuver, BookingInfo bookingInfo) {
log.info(getCheckLinkName() + " start ...");
// 2、其它关键规则校验;
if (!user.getOtherInfo().equals(maneuver.getRule())) {
return new CheckInfo(CheckStatus.ERROR, "规则校验失败");
}
// 如果后面没有了,说明校验通过
if(getNext() == null){
return new CheckInfo(CheckStatus.SUCCESS, "校验通过");
}
// 如果后面还有校验链,再继续执行
return getNext().doCheck(user, maneuver, bookingInfo);
}
}
然后模拟容量校验
@Slf4j
public class CapacityCheck extends CheckLink {
public CapacityCheck(String checkLinkName) {
super(checkLinkName);
}
@Override
public CheckInfo doCheck(User user, Maneuver maneuver, BookingInfo bookingInfo) {
log.info(getCheckLinkName() + " start ...");
// 3、检验此活动是否有剩余容量;
if (maneuver.getCapacity() <= 0) {
return new CheckInfo(CheckStatus.VOLUME_LESS, "剩余容量不足");
}
// 如果后面没有了,说明校验通过
if(getNext() == null){
return new CheckInfo(CheckStatus.SUCCESS, "校验通过");
}
// 如果后面还有校验链,再继续执行
return getNext().doCheck(user, maneuver, bookingInfo);
}
}
当然,有别的校验需求也可以自定义添加,这里就先写三个。让我们来分析一下代码结构,这三个结构都是类似的,看doCheck()方法,首先执行校验内容,如果校验失败,就返回提示信息;如果校验成功,就再判断一下是否处于责任链末端,如果处于责任链末端,说明此责任链已经执行完毕;如果后续还有其他责任链,就传播至下一责任链,以此类推。
4.2.4、运行测试
public static void useResponChainModel(Maneuver maneuver, User user, BookingInfo bookingInfo){
CheckLink checkLink = new ManeuverCheck("1、活动校验")
.setNext(new RuleCheck("2、规则校验")
.setNext(new CapacityCheck("3、容量校验")));
CheckInfo checkInfo = checkLink.doCheck(user, maneuver, bookingInfo);
log.info(String.valueOf(checkInfo));
}
我们创建一条责任链,依次是:活动校验->规则校验->容量校验,然后执行doCheck()方法,获取CheckInfo信息。
public static void main(String[] args) {
// 模拟活动
Maneuver maneuver = new Maneuver("预约活动1",
LocalDateTime.of(2023, 11, 19, 7, 0),
LocalDateTime.of(2023, 11, 19, 19, 0),
1000L,
"man");
// 模拟用户
User user = new User("张三", "man");
// 模拟预约信息
BookingInfo bookingInfo = new BookingInfo("张三", "预约活动1", "...");
// 使用责任链
useResponChainModel(maneuver, user, bookingInfo);
}
可以看到运行结果:
成功触发了我们设置的三个校验类,成功完成了校验。我们将条件修改为不符合,再试一下效果:
可以看到修改条件,将规则校验失败的时候,成功停止了后续的校验,并返回了信息。
至此,我们就完成了使用责任链设计模式重构if-else判断。
五、心得
之前我做过档案管理系统,刚开始的时候没学习过设计模式,硬是一堆if-else堆上去,因为档案审核流程复杂而且多变,经常因为需求变动重构代码。如果是刚写完的代码重构还方便点,因为还记得怎么写的。一旦时间久了,忘记了之前的业务逻辑,再想在“屎山”上修改代码,太难了。后续,我自己摸索出了一条经验,用自己的语言描述就是,使用“黑箱”,即将每个if-else块都封装在一个“黑箱”里面,一个操作进来,就进黑箱,然后这个黑箱执行一些操作,然后告诉我们结果。我自己尝试了重构了所有的if-else,发现还可以,效果不错。后来,我又尝试在数据库中存储各个流程线的流程,当以后再有任务进来,我就去数据库里读流程,然后再通过“黑箱”依次校验。再后来直接上flowable流程引擎了……
直到我偶然间听到设计模式,我就发现,我那个“黑箱”就是责任链的雏形。果然,编程思想都是互通的,如果我早点学习设计模式,也不会走这么多弯路。但是想想走弯路也是有好处的,会让自己对责任链模式的认识更加深刻。
有同学还可能会有这样的疑问,我用if-else,就几行代码,轻松实现;用上设计模式,又要写抽象类,又要写实现类,代码量和复杂性增加了好几倍,图啥?当然,如果是小型项目,可以用if-else,这一点毛病也没用。如果是大型项目且变动多,就可以考虑责任链模式,这个因人而异。我们使用了责任链模式,可以看到代码结构变得清晰了,降低了耦合度,也让对象间的关系变得清晰,所以在后续的开发中,我们可以试着用一下责任链模式。