责任链
最近在开发一个比较复杂的系统,系统中目前写到了工作流(工作流是公司内架构师设计)模块。
某一工作流中针对一个对象的属性、参数校验,而不同的工作流可冗余了其中的某些校验代码。
对象的单独特征的校验在不同的工作流中存在复用,使用责任链模式将各个校验器进行单独开发,然后链起来,以达到高复用的目的。
正文
设计模式在我们日常的开发中是非常热门的一项话题。 开发不是按照设计模式进行死板的开发,而是开发出来后,根据实际场景进行迭代,迭代后发现其满足某项设计模式。 所以别想着死套设计模式到你的代码之中,而是去思考,什么才是最适合自己业务的,这样才能构建一个优美的系统。
本文描述的责任链模式与菜鸟教程之类的有所不同,是针对于系统中要对某些对象进行校验的一个校验责任链。
场景描述
开始之前我们先讲述一下我的设计场景
如果你要出去放风筝,首先你要考虑如下因素:
- 天气如何
- 今天有空吗
- 出门工具
- 风筝还在吗
- 风筝还具备飞行能力吗
- 时间方便吗
当然,除了放风筝这件事,你发现你干其他事情的时候,都需要考虑其中的某些因素,比如工作日要考虑天气和出门工具等等。 所以我们将这些因素设置为单独的校验器。然后创建一个链条(事件),每次就可以直接去执行这个联调里面的校验事项。
我们设定一个检验接口 checkHandler ,下面有不同的实现:天气校验、空闲校验、对象存在校验等 接口方法:check(T data);
创建校验器之后,我们再创建一个责任链类,用于存放这些校验器。
针对与不同的事件就是不同的责任链,我们只需要顺序放入校验器,执行校验器的校验方法doChain(T)
, dochain(T)
中,是顺序遍历 LinkedList-chain
执行其中的校验方法。
创建一个放风筝Chain:
// 伪代码
CheckChain<风筝> chain = new CheckChain();
chain.addHandlers(new 空闲校验(), new 天气校验(), 风筝存在校验(), new出行工具校验());
然后运行这个chain
// 伪代码
chain.doChain(风筝);
当然,你在其他事务中可能也会宠用其中的空闲、天气、出行工具校验,直接套进去即可。 这样就完成了一个实际应用版的责任链模式。
实战
我们再写一个实例程序,用来校验User。
1 依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
2 校验流程
- 用户是否存在,其基本属性是否存在
- 性别校验 (0:女 1:男)
- 年龄校验 (0 - 200)
3 实体
首先创建User类,其中有三个属性:
- 姓名
- 性别
- 年龄
@Getter
public class User {
private final String name;
private final Integer age;
private final Integer sex;
public User(String name, Integer age, Integer sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
然后定义校验器接口 <<interface>> CheckHandler
4 检验器接口
public interface CheckHandler<T> {
/**
* 校验数据
* @param data 数据
*/
public void check(T data);
}
所有的校验器都需要实现方法:check(T data)
这里创建三个实现类
校验器-用户属性非空验证
@Slf4j
public class UserParamNotNullCheck<T extends User> implements CheckHandler<T> {
@Override
public void check(User user) {
if (null == user || null == user.getSex() || null == user.getAge() || null == user.getName()) {
throw new RuntimeException("xxx参数为空");
} else {
log.info("非空验证通过, {}", user);
}
}
}
校验器-用户性别
@Slf4j
public class UserSexCheck<T extends User> implements CheckHandler<T> {
@Override
public void check(User user) {
Integer sex = user.getSex();
if (0 > sex || sex > 1) {
throw new RuntimeException("性别不存在:" + sex);
} else {
log.info("性别验证通过, {} 性", user.getSex() == 1 ? "男" : "女");
}
}
}
校验器-用户年龄
@Slf4j
public class UserAgeCheck<T extends User> implements CheckHandler<T> {
private final Integer MIN_AGE = 0;
private final Integer MAX_AGE = 200;
@Override
public void check(User user) {
Integer age = user.getAge();
if (MIN_AGE > age || MAX_AGE < age) {
throw new RuntimeException("年龄异常: " + age);
} else {
log.info("年龄验证通过, {} 岁", user.getAge());
}
}
}
5 责任链类-存储校验器
创建责任链之后,往里面塞入需要检验的类,最后调用责任链的doChain(Data)
,就会依次执行责任链里面的校验方法。
public class CheckChain<T> {
private final LinkedList<CheckHandler<T>> handlerChain;
public CheckChain() {
this.handlerChain = new LinkedList<>();
}
public void addHandler(CheckHandler<T> handler) {
handlerChain.add(handler);
}
@SafeVarargs
public final void addHandlers(CheckHandler<T>...handlers) {
Arrays.stream(handlers).sequential().forEach(this::addHandler);
}
/**
* 执行责任链中的校验器的校验
* @param t
*/
public void doChain(T t) {
while (handlerChain.size() > 0) {
CheckHandler<T> tCheckHandler = handlerChain.pollFirst();
tCheckHandler.check(t);
}
}
}
6 测试
public class ExecutorMain {
public static void main(String[] args) {
CheckChain<User> checkChain = new CheckChain<>();
checkChain.addHandlers(new UserParamNotNullCheck<>(), new UserSexCheck<>(), new UserAgeCheck<>());
checkChain.doChain(new User("Dcpnet", 22, 1));
}
}
/**
output:
15:03:18.490 [main] INFO cn.dcpnet.springstudy.check.UserParamNotNullCheck - 非空验证通过, User{name='Dcpnet', age=22, sex=1}
15:03:18.492 [main] INFO cn.dcpnet.springstudy.check.UserSexCheck - 性别验证通过, 男 性
15:03:18.492 [main] INFO cn.dcpnet.springstudy.check.UserAgeCheck - 年龄验证通过, 22 岁
*/
不同于菜鸟教程中,其实很多地方的责任链模式也是如同这样使用的一个list去持有的链,所以说不要套着设计模式开发,要根据实际业务进行设计模式。
加油,奋斗中的少年,在未来的光明之中必有你的一席之地,向光而行。