一、责任链的基本概念
科普中国在百科中对责任链进行了这样的描述:“责任链模式是一种设计模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求”。简要流程如下图所示:
二、责任链的好处
假设有这样的一个业务场景:“客户端向服务端进行消息上报,由于客户端数据质量参差不齐,服务端需要对其进行防护,比如判断这个消息对象是否存在、判断这个消息对象的属性是否合法、判断这个消息对象xxxx等”。若使用常规方式实现:
public FunResult<String> receive(Message message) {
// 判断信息对象是否重复
if (checkRepeat(message)) {
return FunResult.<String>builder().fail().message("消息已存在, 请勿重复上报").build();
}
// 校验信息对象内字段是否合法
if (checkField(message)) {
// 这里若需要返回详细字段错误提示语, 逻辑可能会更加复杂
return FunResult.<String>builder().fail().message("消息字段校验失败, 请核实后上报").build();
}
// 这里进行一些其他的校验
if (checkXxx(message)) {
return FunResult.<String>builder().fail().message("xxxxxx").build();
}
// ... 其他的校验逻辑和业务逻辑 ...
return FunResult.<String>builder().success().message("上报信息接收成功").build();
}
常规的编写方式存在两个明显弊端:
- 代码臃肿,现实的业务场景远远比示例要复杂得多,这样就会导致这个类非常的庞大和臃肿。
- 高度耦合,如果想新增校验逻辑或者改变校验顺序只能对代码进行大面积的调整。
如果使用责任链模式对上诉流程进行重构,将每个 if...else 判断规划成对应的校验器节点,这样不仅可以提升业务代码的可读性,还可以大大降低耦合度,并且方便我们对校验顺序调整以及校验方式的扩充。
三、责任链的简单实现(其一)
通过第一节我们得知“责任链的每个节点对象通常会持有下一个对象的引用”,看到这段话大家肯定能够想到 “单向链表结构”,这一节就是参考 “单向链表结构” 来实现责任链模式。
首先,我们实现一个抽象的 “链节点” 这个链接点中包含 “下个对象的引用” 和一个 “抽象的处理方法” :
/**
* 抽象的链节点
* @author: younnng
* @param <E> 在链上传递的对象泛型
* @param <R> 期望返回的结果集泛型
*/
public abstract class ChainNode<E, R> {
/**
* 下一个节点
*/
protected ChainNode<E, R> next;
/**
* 执行处理操作
* @param element 在每个链节点上传递的对象
* @return 返回结果集
*/
public abstract R process(E element);
/**
* 给节点赋值
* @param next 下一个节点
* @return 第一个节点
*/
public ChainNode<E, R> setNext(ChainNode<E, R> next) {
this.next = next;
return next;
}
}
定义完链节点后,我们对第二节的业务场景进行简单重构,先定义 “校验节点” :
@Slf4j
public class CheckRepeatNode extends ChainNode<Message, FunResult<String>> {
@Override
public FunResult<String> process(Message message) {
log.debug("...进行重复校验...");
// ... 执行校验逻辑, 这里默认成功 ...
log.debug("...重复校验完毕, 准备传递至下一个节点...");
return this.next == null ? FunResult.<String>builder().success().message("信息校验成功").build()
: this.next.process(message);
}
}
@Slf4j
public class CheckFieldNode extends ChainNode<Message, FunResult<String>> {
@Override
public FunResult<String> process(Message message) {
log.debug("...进行校验信息对象字段...");
// ... 执行校验逻辑, 这里默认成功 ...
log.debug("...信息对象字段校验完毕, 准备传递至下一个节点...");
return this.next == null ? FunResult.<String>builder().success().message("信息校验成功").build()
: this.next.process(message);
}
}
@Slf4j
public class CheckXxxNode extends ChainNode<Message, FunResult<String>> {
@Override
public FunResult<String> process(Message message) {
log.debug("...进行xxx校验...");
// ... 执行校验逻辑, 这里默认成功 ...
log.debug("...xxx校验完毕, 准备传递至下一个节点...");
return this.next == null ? FunResult.<String>builder().success().message("信息校验成功").build()
: this.next.process(message);
}
}
然后,我们进行简单的流程验证,示例代码如下:
@Slf4j
public class Test {
public static void main(String[] args) {
log.debug("{}", receive().getMsg());
}
public static FunResult<String> receive() {
ChainNode<Message, FunResult<String>> chain = getChain();
FunResult<String> process = chain.process(new Message());
log.debug("{}", process.getMsg());
if (!process.isSuccess()) {
return process;
}
// ... 业务逻辑 ...
return FunResult.<String>builder().success().message("上报信息接收成功").build();
}
public static ChainNode<Message, FunResult<String>> getChain() {
// 这里是示例, 现实业务这段链的构造和组装可以抽取
CheckRepeatNode checkRepeatNode = new CheckRepeatNode();
checkRepeatNode.setNext(new CheckFieldNode()).setNext(new CheckXxxNode());
return checkRepeatNode;
}
}
示例代码会返回在控制台打印如下的结果:
19:23:56.941 [main] DEBUG o.g.younnng.io.example.CheckRepeatNode - ...进行重复校验...
19:23:56.944 [main] DEBUG o.g.younnng.io.example.CheckRepeatNode - ...重复校验完毕, 准备传递至下一个节点...
19:23:56.944 [main] DEBUG o.g.younnng.io.example.CheckFieldNode - ...进行校验信息对象字段...
19:23:56.944 [main] DEBUG o.g.younnng.io.example.CheckFieldNode - ...信息对象字段校验完毕, 准备传递至下一个节点...
19:23:56.944 [main] DEBUG o.g.younnng.io.example.CheckXxxNode - ...进行xxx校验...
19:23:56.944 [main] DEBUG o.g.younnng.io.example.CheckXxxNode - ...xxx校验完毕, 准备传递至下一个节点...
19:23:56.948 [main] DEBUG o.g.younnng.io.example.Test - 信息校验成功
19:23:56.953 [main] DEBUG o.g.younnng.io.example.Test - 上报信息接收成功
四、简单优化
上一节,我们封装了一个 ChainNode 并对业务场景进行了简单的重构。但是, ChainNode 作为一个节点对象,它的行为应该是“过程处理”和“向下传递”,由于 setNext() 属于“构造”行为,如下方代码片段所示,我们需要使用一个 CheckRepeatNode 实例去构造链,从而导致 ChainNode 的职责不够单一化。
public static ChainNode<Message, FunResult<String>> getChain() {
CheckRepeatNode checkRepeatNode = new CheckRepeatNode();
checkRepeatNode.setNext(new CheckFieldNode()).setNext(new CheckXxxNode());
return checkRepeatNode;
}
这就好比,你们团队的项目经理,既要负责协调团队派发任务,又要负责代码编写。这能不乱套么?那么,我们就需要抽象出一个“管理者”,让“管理者”去“调配构造”和“下令实施”。
首先,我们定义一个抽象接口 Chain 来约束行为:
/**
* 抽象的Chain
* @author: younnng
* @param <E> 在链上传递的对象泛型
* @param <R> 期望返回的结果集泛型
*/
public interface Chain<E, R> {
/**
* 构造链
* @param next 下一个链节点
* @return 默认链
*/
Chain<E, R> append(ChainNode<E, R> next);
/**
* 执行与实施
* @param element 在每个链节点上传递的对象
* @return 最终结果集
*/
R execute(E element);
/**
* 抽象的链节点
*/
public abstract static class ChainNode<E, R> {
/**
* 下一个节点
*/
protected ChainNode<E, R> next;
/**
* 过程处理
* @param element 在每个链节点上传递的对象
* @return 返回结果集
*/
public abstract R process(E element);
/**
* 向下传递
* @param element 在每个链节点上传递的对象
* @param result 期望构造和返回的结果集
* @return 返回结果集
*/
public R transmit(E element, R result) {
if (this.next == null) {
return result;
}
return this.next.process(element);
}
}
}
然后,编写一个默认的 Chain 实现 DefaultChain 用于真正的“调配构造”和“下令实施”:
/**
* 默认实现
* @author: younnng
* @param <E> 在链上传递的对象泛型
* @param <R> 期望返回的结果集泛型
*/
public class DefaultChain<E, R> implements Chain<E, R> {
/**
* 指向第一个节点
*/
private ChainNode<E, R> first;
/**
* 指向最后一个节点
*/
private ChainNode<E, R> last;
public DefaultChain() {
super();
}
public static <E, R> DefaultChain<E, R> build() {
return new DefaultChain<>();
}
public DefaultChain<E, R> append(ChainNode<E, R> next) {
if (first == null) {
first = next;
} else {
last.next = next;
}
last = next;
return this;
}
@Override
public R execute(E element) {
return this.first.process(element);
}
}
优化后,编写简单的测试,构建三个节点 AChainNode BChainNode CChainNode 对数字进行 +1 操作:
@Slf4j
public class AChainNode extends ChainNode<Integer, FunResult<Integer>> {
@Override
public FunResult<Integer> process(Integer element) {
log.debug("节点A执行+1");
element += 1;
log.debug("节点A开始向下传递");
return this.transmit(element, FunResult.<Integer>builder().success().message("执行成功").data(element).build());
}
}
@Slf4j
public class BChainNode extends ChainNode<Integer, FunResult<Integer>> {
@Override
public FunResult<Integer> process(Integer element) {
log.debug("节点B执行+1");
element += 1;
log.debug("节点B开始向下传递");
return this.transmit(element, FunResult.<Integer>builder().success().message("执行成功").data(element).build());
}
}
@Slf4j
public class CChainNode extends ChainNode<Integer, FunResult<Integer>> {
@Override
public FunResult<Integer> process(Integer element) {
log.debug("节点C执行+1");
element += 1;
log.debug("节点C开始向下传递");
return this.transmit(element, FunResult.<Integer>builder().success().message("执行成功").data(element).build());
}
}
编写测试,构造并执行整个链路获取结果集:
@Slf4j
public class Test {
public static void main(String[] args) {
// 构造
DefaultChain<Integer, FunResult<Integer>> chain = DefaultChain.<Integer, FunResult<Integer>>build()
.append(new AChainNode()).append(new BChainNode()).append(new CChainNode());
// 执行
FunResult<Integer> result = Optional.ofNullable(chain.execute(0))
.orElse(FunResult.<Integer>builder().fail().build());
log.debug("返回消息为 : {}, 计算结果为 : {}", result.getMsg(), result.getData());
}
}
执行结果如下所示:
20:01:35.570 [main] DEBUG o.g.younnng.io.example.AChainNode - 节点A执行+1
20:01:35.574 [main] DEBUG o.g.younnng.io.example.AChainNode - 节点A开始向下传递
20:01:35.579 [main] DEBUG o.g.younnng.io.example.BChainNode - 节点B执行+1
20:01:35.579 [main] DEBUG o.g.younnng.io.example.BChainNode - 节点B开始向下传递
20:01:35.579 [main] DEBUG o.g.younnng.io.example.CChainNode - 节点C执行+1
20:01:35.579 [main] DEBUG o.g.younnng.io.example.CChainNode - 节点C开始向下传递
20:01:35.580 [main] DEBUG o.g.younnng.io.example.Test - 返回消息为 : 执行成功, 计算结果为 : 3
五、结语
经过简单优化后,我们得到了一个 Chain 模板接口,以及默认实现 DefaultChain 。DefaultChain就像一个真正的 leader 能够很好的协调团队并派发任务。