简单工厂+责任链模式实现任务完整性校验

序言

现在有这样一个场景:

作业结项时需要对每个 Label 中的数据完整性进行校验。你可以将 Label 理解为不同的标签,每个标签中都需要完成特定的内容,一个作业可以配置多个 Label

看到这个需求,我们二话不说,直接在作业结项接口中新增代码,对每个 Label 进行校验操作。

这样有问题吗?当然没问题!(你看着被代码塞满的类心虚的说到)

一顿操作猛如虎,代码 Review 骂成狗 ~

作为一名优雅(自认为)的程序员,绝不允许这种情况出现!

这时候我们就需要考虑使用设计模式来应对了。

使用了设计模式,我们可以做到在结项方法中只新增一行代码即可实现需求(歪嘴战神)~

怎么选择设计模式

使用设计模式的难点往往在于如何选用,因为只有选对了方法才能事半功倍。

这里我们对每个 Label 都有不同的处理,所以我们需要为每个 Label 都需要新建一个 handler ,将每个 Label 的校验操作解耦和隔离,使它们互不影响,将来要对其中某个 Label 修改时,也可以做尽量少的操作。

同时我们可以将所有 Label 的校验操作串成一条执行链条,只要某个节点校验到不满足条件即可提前退出,而不需要再执行后续操作。对于满足条件的节点则进入下一个节点继续校验。直到所有 Label 校验完毕。

都说到这个份上了,想必大家也知道什么设计模式适合实现这个需求了,没错,就是简单工厂模式和责任链模式。

本文不对上述两个模式做详细介绍,直接上代码。

简单工厂

我们需要对不同的 Label 获取不同的 handler 处理类,一般我们可以通过策略模式 + 简单工厂的方式来根据 Label 标签类型来创建 handler 处理类。

这里我通过将所有 handler 实现类交由 spring 容器管理,再根据需要通过 Label 类型获取,而不必每次获取都去创建。

定义所有处理类的超类

首先我们需要定义一个被所有 Label 继承的超类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class OperationResultHandler {

protected OperationResultHandler handler;

// 设置下一个执行节点
public void setSuperior(OperationResultHandler handler) {
this.handler = handler;
}

// 返回当前实现类的type
public abstract LabelEnum getType();

// 执行具体的校验操作
public abstract Pair<Boolean, LabelEnum> handler(DutyRequest request);
}

定义各个Label的实现类

由于 Label 数量比较多,这里只列举一个实现类,所有实现类除具体校验逻辑外均一致。

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
@Component
@RequiredArgsConstructor
public class OrganizeVerifyHandler extends OperationResultHandler {

private final QuestionnaireMapper questionnaireMapper;

@Override
public LabelEnum getType() {
return LabelEnum.ZZDY;
}

@Override
public Pair<Boolean, LabelEnum> handler(DutyRequest request) {
// 如果作业包含当前节点才对当前节点校验,否则进入下一个节点
if (request.getServiceIds().contains(getType().getCode())) {
// 具体的校验逻辑,不满足直接跳出,满足且下一个节点不为空继续校验
Integer count = questionnaireMapper.selectCount(new QueryWrapper<Questionnaire>()
.eq("operation_id", request.getOperationId())
.eq("object_id", getType().getCode())
.eq("finished", false));
if (count > 0)
return new Pair<>(Boolean.FALSE, getType());
}
// 下一个流程链不为空继续校验,否则为最后一个节点,返回true校验通过
if (handler != null)
return handler.handler(request);
else
return new Pair<>(Boolean.TRUE, getType());
}
}

Spring容器管理

我们可以将所有 Label 的实现类交给 Spring 容器管理, 包括生产 handler 的工厂,需要使用时再通过 getHandler() 方法传入入参 Label 的类型获取。

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
@Component
public class OperationResultHandlerFactory implements InitializingBean, ApplicationContextAware {

private static ApplicationContext applicationContext;

private static final Map<LabelEnum, Supplier<OperationResultHandler>> BEAN_MAP = new HashMap<>();

/**
* 根据label标签获取对应的处理器
*
* @param type {@see LabelEnum}
* @return return handler with type, null if not found
*/
public static OperationResultHandler getHandler(LabelEnum type) {
Supplier<OperationResultHandler> supplier = BEAN_MAP.get(type);
return Objects.isNull(supplier) ? null : supplier.get();
}

@Override
public void afterPropertiesSet() {
applicationContext.getBeansOfType(OperationResultHandler.class).values()
.forEach(bean -> BEAN_MAP.put(bean.getType(), () -> bean));
}

@Override
public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
OperationResultHandlerFactory.applicationContext = applicationContext;
}
}

责任链

各节点公共入参

定义每个 Label 实现类需要用的公共参数,随着责任链一直传递:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
public class DutyRequest {

// 作业id
private String operationId;

// 当前作业包含的label类型
private Set<Long> serviceIds;

public DutyRequest(String operationId, Set<Long> serviceIds) {
this.operationId = operationId;
this.serviceIds = serviceIds;
}
}

构建责任链

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
@Component
@RequiredArgsConstructor
public class OperationAbilityHandler {

private final CoOperationMapper coOperationMapper;

/**
* 根据作业id获取作业执行列表构建作业检测链,按顺序检测所有标签是否完成(是否有数据)
*
* @param operation 作业id
*/
public void abilityChainVerify(String operation){
// 作业执行列表
CoOperation coOperation = coOperationMapper.selectById(operation);
if (Objects.isNull(coOperation))
throw new ServiceException("作业不存在");
Set<Long> serviceIds = StrUtil.split(coOperation.getServiceContent(), StrUtil.COMMA).stream().map(Long::parseLong).collect(Collectors.toSet());
serviceIds.add(LabelEnum.XTDY.getCode());
DutyRequest request = new DutyRequest(operation, serviceIds);

// 标签列表(有序)
List<LabelEnum> labels = Arrays.stream(LabelEnum.values()).filter(l -> l.getPCode() != 0L).collect(Collectors.toList());

OperationResultHandler head = null;
OperationResultHandler currentHandler = null;

// 构建作业检测链
for (LabelEnum label : labels) {
OperationResultHandler handler = OperationResultHandlerFactory.getHandler(label);

if (handler != null) {
if (currentHandler == null) {
// 如果当前处理程序为空,表示这是责任链的第一个节点
head = handler;
} else {
// 将当前处理程序的下一个节点设置为新的处理程序
currentHandler.setSuperior(handler);
}
currentHandler = handler;
}
}

assert head != null;
Pair<Boolean, LabelEnum> pair = head.handler(request);
if (!pair.getKey())
throw new ServiceException(String.format("作业结果校验不通过,[%s]功能未完成", pair.getValue().getName()));
}
}

注入检测代码

然后我们只需要在原方法中合适位置引入一行代码即可完成作业的完整性校验:

1
operationAbilityHandler.abilityChainVerify(operation.getOperationId());

总结

通过使用设计模式,我们将各个模块的耦合度降到最低,有利于后续的维护和迭代工作。


简单工厂+责任链模式实现任务完整性校验
https://seeyourface.cn/2023/10/27/简单工厂+责任链模式实现任务完整性校验/
作者
Yang Lei
发布于
2023年10月27日
许可协议