前言(忽悠)
之前阅读了一些开源框架的源码,发现责任链模式就像一个xxx(此处词穷,大佬们评论区帮我填词0.0)一样,应用在各大框架中。并且有多种实现方式,下面的内容好好看,好好学。面试时,让你能跟面试官多过几招,工作中,让领导对你刮目相看。
初识
责任链:主要用于流程控制和处理,像请假的审批流程,工厂里面的流水线,devops的pipeline(流水线)等,都可以看做责任链。
简介(鬼话)
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。
责任链模式是一种对象行为型模式,其主要优点如下。
- 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
- 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
- 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
其主要缺点如下。
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
引用自:http://c.biancheng.net/view/1...
目的:将请求方与处理方解耦,链式调用,可以灵活改变处理顺序,符合开闭原则,可扩展性强。
从实际出发(人话)
三个要素:
- 责任链(审批流程)
- 处理人(审批人)
- 链中传递的信息(审批内容)
代码示例
定义审批人的抽象类,「 设计模式的惯用套路,将可能有多个实现的类定义为抽象,提高程序扩展性 」,本示例中审批人可能会有HR,经理,技术总监,小组长等等...
/**
* 责任链中的节点(流程中的审批人)
* @param <T>
* 这里将审批的信息定义为泛型,更灵活
*/
@Data
public abstract class ApplyHandler<T> {
protected ApplyHandler<?> next = null;
//定义该审批人在整个责任链中的审批顺序
protected Integer order;
protected String name;
public void apply(Object obj) {
if (null != next) {
next.transform(obj);
}else {
System.out.println("-----------------------------");
System.out.println("end");
}
}
@SuppressWarnings("unchecked")
public void transform(Object obj){
//将传入的对象转换为泛型定义的类型
T param = (T)obj;
handle(param);
}
//具体的审批逻辑
protected abstract void handle(T param);
public ApplyHandler(Integer order) {
this.order = order;
}
public ApplyHandler() {
}
}
创建抽象责任链(审批流程)并创建一个默认链(默认审批流程)。为后面创建的其他类型的审批流程提供一个可扩展的口子。
/**
* 责任链的管理者
* 管理链头尾,以及链条的增删改查
*/
@Data
public abstract class ApplyChain {
public ApplyHandler<?> first = new ApplyHandler<Object>() {
@Override
public void handle(Object obj) {
//第一个节点不参与处理,直接往下申请
apply(obj);
}
};
private ApplyHandler<?> end = first;
//责任链的调用入口
protected abstract void exc(Object obj);
//向尾部添加节点
public void addLast(ApplyHandler<?> handler){
end.setNext(handler);
end = handler;
}
//...此处省略了其他操作责任链的方法
}
/**
* 默认审批流程
*/
class DefaultApplyChain extends ApplyChain {
@Override
protected void exc(Object obj) {
//使用链头开始调用整条责任链
System.out.println("--------------- start --------------");
first.transform(obj);
}
}
定义不同的实际审批人
public class GroupLeaderHandler extends ApplyHandler<LeaveApplyMsg> {
public GroupLeaderHandler(Integer order) {
super(order);
}
@Override
protected void handle(LeaveApplyMsg applyMsg) {
System.out.println("小组长审批");
//审批完成后继续向下申请
apply(applyMsg);
}
}
class ManagerHandler extends ApplyHandler<LeaveApplyMsg> {
public ManagerHandler(Integer order) {
super(order);
}
@Override
protected void handle(LeaveApplyMsg applyMsg) {
System.out.println("经理审批");
//审批完成后继续向下申请
apply(applyMsg);
}
}
class HrHandler extends ApplyHandler<LeaveApplyMsg> {
public HrHandler(Integer order) {
super(order);
}
@Override
protected void handle(LeaveApplyMsg applyMsg) {
System.out.println("HR审批");
//审批完成后继续向下申请
apply(applyMsg);
}
}
定义请假信息
/**
* 请假审批信息
*/
@Data
public class LeaveApplyMsg {
private String msg;
private Integer level;
private String name;
private Integer hour;
}
模拟用户提交请假申请
public class ChainClient {
public static void main(String[] args) {
//该list可以从数据库中的某个表查询出来
List<ApplyHandler<LeaveApplyMsg>> applyHandlerList = new ArrayList<>();
ApplyHandler<LeaveApplyMsg> hr = new HrHandler(3);
ApplyHandler<LeaveApplyMsg> gl = new GroupLeaderHandler(1);
ApplyHandler<LeaveApplyMsg> m = new ManagerHandler(2);
applyHandlerList.add(m);
applyHandlerList.add(gl);
applyHandlerList.add(hr);
//根据order字段排序
List<ApplyHandler<LeaveApplyMsg>> collect = applyHandlerList.stream().sorted(Comparator.comparing(ApplyHandler::getOrder)).collect(Collectors.toList());
ApplyChain applyChain = new DefaultApplyChain();
//循环的组装到责任链中
for (ApplyHandler<?> applyHandler : collect) {
applyChain.addLast(applyHandler);
}
//在实际场景中,上面的代码可以放到系统启动的时候初始化数据的方法中,spring有很多这样的扩展点,可以自行了解
//这下面的代码就相当于,用户在前端选择好审批类型(对应不同的信息),然后点击申请
//后端就触发applyChain.exc(applyMsg);这个方法开始执行整个责任链
LeaveApplyMsg applyMsg = new LeaveApplyMsg();
applyMsg.setMsg("apply leave");
applyMsg.setName("lhm");
applyMsg.setLevel(1);
applyChain.exc(applyMsg);
}
}
开源框架中责任链的使用
spring-cloud-gateway
中请求过滤就使用了责任链模式,入口在FilteringWebHandler
类中。shenyu
网关中各个插件的调用也使用了责任链模式,入口在ShenyuWebHandler
类中。
sentinel中对各个slot插槽的调用使用责任链模式,入口可查看CtSph
#asyncEntryWithPriorityInternal
方法的下面这行代码
小伙伴们有兴趣可以去看看这些框架中是如何实现责任链模式的。看懂示例代码很容易搞懂sentinel的使用方式,gateway和shenyu都是网关,他们实现责任链的方式类似,使用的是响应式编程,我们可以看看实现思路。
下面是spring-cloud-gateway的实现方式
private static class DefaultGatewayFilterChain implements GatewayFilterChain {
//控制当前使用列表中哪个filter处理
private final int index;
//存放所有的filter(也就是这种实现方式采用list作为责任链)
private final List<GatewayFilter> filters;
DefaultGatewayFilterChain(List<GatewayFilter> filters) {
this.filters = filters;
this.index = 0;
}
private DefaultGatewayFilterChain(DefaultGatewayFilterChain parent, int index) {
this.filters = parent.getFilters();
this.index = index;
}
public List<GatewayFilter> getFilters() {
return filters;
}
//链式调用的入口
@Override
public Mono<Void> filter(ServerWebExchange exchange) {
return Mono.defer(() -> {
//判断是否调用完成
if (this.index < filters.size()) {
//根据index获取当前需要调用的节点
GatewayFilter filter = filters.get(this.index);
//创建新的chain
DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this,
this.index + 1);
//调用filter实际处理逻辑
return filter.filter(exchange, chain);
}
else {
return Mono.empty(); // complete
}
});
}
}
查看其中一个GatewayFilter实现类的filter逻辑(该代码位于StripPrefixGatewayFilterFactory
类中)
可以看到通过调用filter.filter(exchange, chain);
方法处理实际的filter逻辑,在最后又会调用chain.filter
方法回到链式调用的入口处。这样就产生了循环调用,通过改变index的值,挨个获取list中的filter对象,这样就实现了对filters列表中各filter的链式调用。
总结
责任链模式在很多框架都有应用,学懂该模式,有助于对框架源码的理解,当业务有需求的时候,根据实际情况也可考虑引入责任链模式。不难看出设计模式只是一种思想并没有一个固定写法。所以不同的框架可能有不同的实现方式(根据实际项目情况来定)。希望看完这篇文章的小伙伴也可以有自己的思考,实现一个属于你自己写法的责任链模式吧!
学习设计模式的一点体会:最开始学习设计模式的时候总感觉每次学懂了,也自己写了代码,但是很快就会忘记了,最后还是一个你不认识他,他也不认识你的状态,而且有些设计模式很容易搞混。那应该怎么样才能测地掌握各种设计模式呢???好的,我是kangrooking,关注我,欲知后事如何请听下集分晓 0_0
微信公众号「 袋鼠先生的客栈 」,你们的三连,是我的饲料~~。点赞👍 关注❤️ 分享👥