设计模式之美----职责链模式

设计模式之美—-职责链模式

本文将对责任链模式(职责链模式)分为四个部分进行讲解

  • 原理
  • 实现
  • 应用
  • 练习

让大家系统全面的学会使用责任链模式, 并应用到开发中去

原理篇

大部分设计模式的核心是应对工程中的复杂性, 使得代码满足开闭原则, 提高代码扩展性

职责链的英文名为Chain Of Responsibility Design Pattern, 在GoF的设计模式中的定义如下

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

​ “将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。”

​ 实际上就是将请求通过类似链表的结构, 通过结点处理并线性的传递下去,直到请求完成或者没有下一个结点

​ 这里的结点是指单个处理请求的对象, 接受上一个结点处理后的结果并在处理后, 传递给下一个结点或者请求完成结束处理, 因为每个结点都承担着细粒度的职责, 所以称为责任链模式

实现篇

​ 设计模式的实现往往多种多样, 可能因为语言特性(枚举单例)也可能因为算法(迭代或者递归),使得代码结构各异, 但核心思想是不会变得, 之后的设计模式, 我都会以Java为例做出实现, 因为Java算是比较典型且纯粹的面向对象编程, 其他语言的同学可以借助chatgpt转语言来理解, 本质是一样的

​ 职责链模式的实现分为三个结构, 分别是抽象处理器类(Handler), 处理器类(XXXHandler), 以及处理链(HandlerChain)

抽象处理器类(Handler)

// 处理器的抽象类, 定义处理器结构, 方便后序扩展
public abstract class Handler {
//指向下一个处理器
protected Handler successor = null;

public abstract void handle();

//抽象处理方法, 具体处理器实现此方法,处理请求
public void setSuccessor(Handler successor) {
this.successor = successor;
}
}

处理器类(XXXHandler)

public class HandlerA extends Handler {
@Override
public boolean handle() {
//判断当前请求是否在该处理器处理完成
boolean handled = false;

//.....处理逻辑

//如果请求在当前处理器处理完成或者当前处理器是责任链上最后一个处理器, 停止传递
if (!handled && successor != null) {
successor.handle();
}
}
}

public class HandlerB extends Handler {
@Override
public boolean handle() {
//判断当前请求是否在该处理器处理完成
boolean handled = false;

//.....处理逻辑

//如果请求在当前处理器处理完成或者当前处理器是责任链上最后一个处理器, 停止传递
if (!handled && successor != null) {
successor.handle();
}
}
}

处理链(HandlerChain)

public class HandlerChain {
//通过头尾两个应用记录处理链位置信息
private Handler head = null;
private Handler tail = null;

//在处理链尾部增加处理器
public void addHandler(Handler handler) {
handler.setSuccessor(null);

if (null == head) {
head = handler;
tail = handler;
return;
}
//保证尾部永远指向最后一个处理器
tail.setSuccessor(handler);
tail = handler;
}
}
//使用

public class Main {
public static void main(String[] args) {
HyuanandlerChain chain = new HandlerChain();
chain.addHandler(new HandlerA());
chain.addHandler(new HandlerB());
chain.handle();
}
}

经典GoF的实现不够优雅, 处理器类中的handle()函数不仅包含业务逻辑, 还有对下一个处理器的调用, 对于不熟悉代码结构的程序员,在添加处理器调用时可能忘记调用下一个处理器, 可能会导致bug, 所以我们利用模板方法模式进行重构(让我想起Spring源码中AbstractBeanFactory中的doGetBean()方法), 将下一个处理器调用的逻辑抽象到模板中

public abstract class Handler {

protected Handler successor = null;

public void setSuccessor(Handler successor) {
this.successor = successor;
}
//模板方法,将处理器调用逻辑抽象成一个final方法
public final void handle() {
boolean handled = doHandle();
if (successor != null && !handled) {
successor.handle();
}
}
//模板方法的扩展点,让子类具体处理器实现处理请求的逻辑
protected abstract boolean doHandle();
}

public class HandlerA extends Handler {
@Override
protected boolean doHandle() {
boolean handled = false;
//...
return handled;
}
}

public class HandlerB extends Handler {
@Override
protected boolean doHandle() {
boolean handled = false;
//...
return handled;
}
}
// HandlerChain和Application代码不变

我们发现责任链模式本质就是将请求 线性传递到每个处理逻辑上进行处理直到某个结点处理成功或者职责链到头

既然是线性结构,我们同样可以使用数组来实现责任链模式, 这种方式更加简单

//数组实现的话,将传递的逻辑放到了HandlerChain中, 只需要实现handle逻辑即可
public interface IHandler {
boolean handle();
}

public class HandlerA implements IHandler {

@Override
public boolean handle() {
boolean handled = false;
//...处理器处理逻辑
//返回是否处理完成
return handled;
}
}
public class HandlerB implements IHandler {
@Override
public boolean handle() {
boolean handled = false;
//...处理器处理逻辑
//返回是否处理完成
return handled;
}
}
public class HandlerChain {
//通过动态数组存储每个处理器
private List<IHandler> handlers = new ArrayList<>();
//在数组中添加处理器
public void addHandler(IHandler handler) {
this.handlers.add(handler);
}
//迭代调用处理器的处理方法
public void handle() {
for (IHandler handler : handlers) {
boolean handled = handler.handle();
//如果在中间某个处理器中处理完成, 提前结束迭代, 不调用下一个处理器
if (handled) {
break;
}
}
}
}
// 使用
public class Application {
public static void main(String[] args) {
HandlerChain chain = new HandlerChain();
chain.addHandler(new HandlerA());
chain.addHandler(new HandlerB());
chain.handle();
}
}

在 GoF 给出的定义中,如果处理器链上的某个处理器能够处理这个请求,那就不会继续往下传递请求。实际上,职责链模式还有一种变体,那就是请求会被所有的处理器都处理一遍,不存在中途终止的情况。

//实现中省略boolean handled = false;即可
public abstract class Handler {
protected Handler successor = null;
public void setSuccessor(Handler successor) {
this.successor = successor;
}
//不需要返回boolean了
public final void handle() {
doHandle();
if (successor != null) {
successor.handle();
}
}
protected abstract void doHandle();
}


public final void handle() {
doHandle();
if (successor != null) {
successor.handle();
}
}

我们参照<<设计模式之美>>中, 灵活扩展算法的敏感词过滤框架来讲

对于支持 UGC(User Generated Content,用户生成内容)的应用(比如论坛)来说,用户生成的内容(比如,在论坛中发表的帖子)可能会包含一些敏感词(比如涉黄、广告、反动等词汇)。针对这个应用场景,我们就可以利用职责链模式来过滤这些敏感词。

对于包含敏感词的内容,我们有两种处理方式,一种是直接禁止发布,另一种是给敏感词打马赛克(比如,用 *** 替换敏感词)之后再发布。第一种处理方式符合 GoF 给出的职责链模式的定义,第二种处理方式是职责链模式的变体。

第一种


public interface SensitiveWordFilter {

boolean doFilter(Content content);

}

//涉黄词过滤, PoliticalWordFilter、AdsWordFilter类代码结构与SexyWordFilter类似,省略了
public class SexyWordFilter implements SensitiveWordFilter {
@Override
public boolean doFilter(Content content) {
boolean legal = true;
//...
return legal;
}
}


//敏感词过滤链
public class SensitiveWordFilterChain {

private List<SensitiveWordFilter> filters = new ArrayList<>();

public void addFilter(SensitiveWordFilter filter) {
this.filters.add(filter);
}

// 如果不包含敏感词则返回ture
public boolean filter(Content content) {

for (SensitiveWordFilter filter : filters) {

if (!filter.doFilter(content)) {
return false;
}

}
return true;
}
}
public class ApplicationDemo {
public static void main(String[] args) {
SensitiveWordFilterChain filterChain = new SensitiveWordFilterChain();
filterChain.addFilter(new AdsWordFilter());
filterChain.addFilter(new SexyWordFilter());
filterChain.addFilter(new PoliticalWordFilter());
boolean legal = filterChain.filter(new Content());
if (!legal) {
// 不发表
} else {
// 发表
}
}
}

第二种

public interface SensitiveWordFilter {
void doFilter(Content content);
}

//涉黄词过滤, PoliticalWordFilter、AdsWordFilter类代码结构与SexyWordFilter类似,省略了
public class SexyWordFilter implements SensitiveWordFilter {
@Override
public void doFilter(Content content) {

//..... 检查是否存在涉黄敏感词,存在则用***替换敏感词

}
}

public class SensitiveWordFilterChain {

private List<SensitiveWordFilter> filters = new ArrayList<>();

public void addFilter(SensitiveWordFilter filter) {
this.filters.add(filter);
}

//
public void filter(Content content) {

for (SensitiveWordFilter filter : filters) {
//如果返回为true,说明中间处理器发现敏感词返回true,这种情况直接结束后序循环
filter.doFilter(content)
}

}
}

你可能会说,我像下面这样也可以实现敏感词过滤功能,而且代码更加简单,为什么非要使用职责链模式呢?这是不是过度设计呢?

public class SensitiveWordFilter {
// return true if content doesn't contain sensitive words.
public boolean filter(Content content) {
if (!filterSexyWord(content)) {
return false;
}
if (!filterAdsWord(content)) {
return false;
}
if (!filterPoliticalWord(content)) {
return false;
}
return true;
}
private boolean filterSexyWord(Content content) {
//....
}
private boolean filterAdsWord(Content content) {
//...
}
private boolean filterPoliticalWord(Content content) {
//...
}
}

​ 应用设计模式主要是为了应对代码的复杂性,让其满足开闭原则,提高代码的扩展性。这里应用职责链模式也不例外

首先, 我们来看, 职责链模式如何应对代码的复杂性

将大的代码逻辑拆分成函数, 将大类拆分成小类, 是应对代码复杂性的常用方法, 职责链模式中,我们把各个敏感词过滤函数继续拆分成独立的类, 进一步简化了SensitiveWordFilter类, 让SenesitiveWordFilter类的代码不会过多, 过复杂.

其次, 我们来看, 职责链模式如何让代码满足开闭原则, 提高代码的扩展性

当我们考虑扩展新的过滤算法的时候, 比如, 我们还需要过滤特殊符号, 按照非职责链模式的代码实现,我们需要修改SensitiveWordFilter的代码, 违反开闭原则, 不过, 这样的修改比较集中,还可以接受的, 而职责链模式的实现方法更加优雅, 只需要新添加一个Filter类, 并且通过addFilter()函数将他添加到FilterChain中即可, 其他代码完全不需要修改

你可能会说,即便使用职责链模式来实现,当添加新的过滤算法的时候,还是要修改客户端代码(ApplicationDemo),这样做也没有完全符合开闭原则。


//实现一个新的处理器
public class NewWordFilter implements SensitiveWordFilter {
public boolean doFilter(Content content) {
boolean legal = true;
//...
return legal;
}
}


public class ApplicationDemo {
public static void main(String[] args) {
SensitiveWordFilterChain filterChain = new SensitiveWordFilterChain();
filterChain.addFilter(new AdsWordFilter());
filterChain.addFilter(new SexyWordFilter());
filterChain.addFilter(new PoliticalWordFilter());
//需要改动客户端代码,在职责链中添加新的处理器
filterChain.addFilter(new NewWordFilter());

boolean legal = filterChain.filter(new Content());
if (!legal) {
// 不发表
} else {
// 发表
}
}
}

细化一下的话,我们可以把上面的代码分成两类:框架代码和客户端代码。其中,ApplicationDemo 属于客户端代码,也就是使用框架的代码。除 ApplicationDemo 之外的代码属于敏感词过滤框架代码。

假设敏感词过滤框架并不是我们开发维护的,而是我们引入的一个第三方框架,我们要扩展一个新的过滤算法,不可能直接去修改框架的源码。这个时候,利用职责链模式就能达到开篇所说的,在不修改框架源码的情况下,基于职责链模式提供的扩展点,来扩展新的功能。换句话说,我们在框架这个代码范围内实现了开闭原则。

除此之外, 利用职责链模式相对于不用职责链模式的实现方法, 还有一个好处, 就是配置过滤算法更加灵活了, 只需要使用某几个过滤算法

应用篇

​ 职责链模式常用在框架的开发中,为框架提供扩展点,让框架的使用者在不修改框架源码的情况下,基于扩展点添加新的功能。实际上,更具体点来说,职责链模式最常用来开发框架的过滤器拦截器

Servlet Filter

Servlet Filter 是 Java Servlet 规范中定义的组件,翻译成中文就是过滤器,它可以实现对 HTTP 请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等。

​ 在实际项目中,我们该如何使用 Servlet Filter 呢?我写了一个简单的示例代码,如下所示。添加一个过滤器,我们只需要定义一个实现 javax.servlet.Filter 接口的过滤器类,并且将它配置在 web.xml 配置文件中。Web 容器启动的时候,会读取 web.xml 中的配置,创建过滤器对象。当有请求到来的时候,会先经过过滤器,然后才由 Servlet 来处理。

public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 在创建Filter时自动调用,
// 其中filterConfig包含这个Filter的配置参数,比如name之类的(从配置文件中读取的)
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("拦截客户端发送来的请求.");
chain.doFilter(request, response);
System.out.println("拦截发送给客户端的响应.");
}
@Override
public void destroy() {
// 在销毁Filter时自动调用
}
}
// 在web.xml配置文件中如下配置:
<filter>
<filter-name>logFilter</filter-name>
<filter-class>com.xzg.cd.LogFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>logFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

​ 我们发现,添加过滤器非常方便,不需要修改任何代码,定义一个实现 javax.servlet.Filter 的类,再改改配置就搞定了,完全符合开闭原则。

Servlet Filter利用的正是职责链模式来保证如此好的扩展性

Servlet 只是一个规范,并不包含具体的实现,所以,Servlet 中的 FilterChain 只是一个接口定义。具体的实现类由遵从 Servlet 规范的 Web 容器来提供,比如,ApplicationFilterChain 类就是 Tomcat 提供的 FilterChain 的实现类

public final class ApplicationFilterChain implements FilterChain {
private int pos = 0; //当前执行到了哪个filter
private int n; //filter的个数
private ApplicationFilterConfig[] filters;
private Servlet servlet;

@Override
public void doFilter(ServletRequest request, ServletResponse response) {
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
filter.doFilter(request, response, this);
} else {
// filter都处理完毕后,执行servlet
servlet.service(request, response);
}
}

public void addFilter(ApplicationFilterConfig filterConfig) {
for (ApplicationFilterConfig filter:filters)
if (filter==filterConfig)
return;
if (n == filters.length) {//扩容
ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n + INCREMENT];
System.arraycopy(filters, 0, newFilters, 0, n);
filters = newFilters;
}
filters[n++] = filterConfig;
}
}

ApplicationFilterChain 中的 doFilter() 函数的代码实现比较有技巧,实际上是一个递归调用。

​ 这样实现主要是为了在一个 doFilter() 方法中,支持双向拦截,既能拦截客户端发送来的请求,也能拦截发送给客户端的响应,你可以结合着 LogFilter 那个例子,以及对比待会要讲到的 Spring Interceptor.

Spring Interceptor

Servlet Filter,有一个功能上跟它非常类似的东西,Spring Interceptor,,都用来实现对 HTTP 请求进行拦截处理。

​ 它们不同之处在于, Servlet FilterServlet 规范的一部分,实现依赖于 Web 容器。Spring InterceptorSpring MVC框架的一部分,由 Spring MVC 框架来提供实现。客户端发送的请求,会先经过 Servlet Filter,然后再经过 Spring Interceptor,最后到达具体的业务代码中。( FilterRequest 抵达Servlet 之前进行过滤 以及Servlet.service()执行完返回reponse之后过滤; 而Interceptor 是在抵达 DispatcherServlet 之后根据请求路径找到对应的映射器候选者后前置处理候选者, 在候选者处理请求并视图解析器处理完成后后置处理)

​ 在项目中,我们该如何使用 Spring Interceptor 呢?

  • 声明拦截器类, 实现HandlerInterceptor接口, 实现preHandler(), postHandler(), ,afterCompletion()方法
  • 通过配置文件中添加<mvc:interceptor>, 配置<mvc:mapping path="拦截路径"/> , <bean class="拦截器实现类全限定名" />(也可以使用注解配置类)

LogInterceptor 实现的功能跟刚才的 LogFilter 完全相同,只是实现方式上稍有区别。LogFilter 对请求和响应的拦截是在 doFilter() 一个函数中实现的,而 LogInterceptor 对请求的拦截在 preHandle() 中实现,对响应的拦截在 postHandle() 中实现。

public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在处理请求之前被调用
// 在此处可以执行一些预处理操作,如记录日志

System.out.println("拦截客户端发送来的请求.");
return true; // 继续后续的处理
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在处理请求后,视图渲染之前被调用
// 在此处可以执行一些后处理操作,如修改响应内容或添加额外的响应头

System.out.println("拦截发送给客户端的响应.");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在请求处理完成后被调用,即视图渲染完成后被调用
// 在此处可以执行一些清理工作,如释放资源或记录请求处理结果

System.out.println("这里总是被执行.");
}
}

//在Spring MVC配置文件中配置interceptors
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*"/>
<bean class="com.xzg.cd.LogInterceptor" />
</mvc:interceptor>
</mvc:interceptors>

Spring interceptor

​ 也是基于职责链模式实现的。其中,HandlerExecutionChain 类是职责链模式中的处理器链。它的实现相较于 Tomcat 中的 ApplicationFilterChain 来说,逻辑更加清晰,不需要使用递归来实现,主要是因为它将请求和响应的拦截工作,拆分到了两个函数中实现。

HandlerExecutionChain 的源码如下所示,同样对代码也进行了一些简化,只保留了关键代码。

/**
* HandlerExecutionChain 类定义了一个拦截器的执行链,为处理器提供拦截功能。
*/
public class HandlerExecutionChain {
private final Object handler; // 处理器对象

private HandlerInterceptor[] interceptors; // 拦截器数组

/**
* 添加一个拦截器到拦截器数组中
*
* @param interceptor 要添加的拦截器
*/
public void addInterceptor(HandlerInterceptor interceptor) {
initInterceptorList().add(interceptor);
}

/**
* 在处理器执行前调用拦截器的预处理方法
*
* @param request HTTP 请求对象
* @param response HTTP 响应对象
* @return 是否继续执行处理器
* @throws Exception
*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
}
}
return true;
}

/**
* 在处理器执行后调用拦截器的后处理方法
*
* @param request HTTP 请求对象
* @param response HTTP 响应对象
* @param mv 处理器返回的 ModelAndView 对象
* @throws Exception
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}

/**
* 在请求完成时调用拦截器的完成处理方法
*
* @param request HTTP 请求对象
* @param response HTTP 响应对象
* @param ex 请求处理过程中出现的异常(如果有)
* @throws Exception
*/
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
} catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
}

​ 在 Spring 框架中,DispatcherServletdoDispatch() 方法来分发请求,它在真正的业务逻辑执行前后,执行 HandlerExecutionChain 中的 applyPreHandle()applyPostHandle() 函数,用来实现拦截的功能。

练习篇

尝试优化一下代码(案例来源)

在本案例中我们模拟在618大促期间的业务系统上线审批流程场景

像是这些一线电商类的互联网公司,阿里、京东、拼多多等,在618期间都会做一些运营活动场景以及提供的扩容备战,就像过年期间百度的红包一样。但是所有开发的这些系统都需要陆续的上线,因为临近618有时候也有一些紧急的调整的需要上线,但为了保障线上系统的稳定性是尽可能的减少上线的,也会相应的增强审批力度。就像一级响应、二级响应一样。

而这审批的过程在随着特定时间点会增加不同级别的负责人加入,每个人就像责任链模式中的每一个核心点。对于研发小伙伴并不需要关心具体的审批流程处理细节,只需要知道这个上线更严格,级别也更高,但对于研发人员来说同样是点击相同的提审按钮,等待审核。

接下来我们就模拟这样一个业务诉求场景,使用责任链的设计模式来实现此功能。

场景简述

模拟审核服务

public class AuthService {

private static Map<String, Date> authMap = new ConcurrentHashMap<String, Date>();

public static Date queryAuthInfo(String uId, String orderId) {
return authMap.get(uId.concat(orderId));
}

public static void auth(String uId, String orderId) {
authMap.put(uId.concat(orderId), new Date());
}

}

  • 这里面提供了两个接口一个是查询审核结果(queryAuthInfo)、另外一个是处理审核(auth)。
  • 这部分是把由谁审核的和审核的单子ID作为唯一key值记录到内存Map结构中。

待优化代码

public class AuthController {

private SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 时间格式化

public AuthInfo doAuth(String uId, String orderId, Date authDate) throws ParseException {

// 三级审批
Date date = AuthService.queryAuthInfo("1000013", orderId);
if (null == date) return new AuthInfo("0001", "单号:", orderId, " 状态:待三级审批负责人 ", "王工");

// 二级审批
if (authDate.after(f.parse("2020-06-01 00:00:00")) && authDate.before(f.parse("2020-06-25 23:59:59"))) {
date = AuthService.queryAuthInfo("1000012", orderId);
if (null == date) return new AuthInfo("0001", "单号:", orderId, " 状态:待二级审批负责人 ", "张经理");
}

// 一级审批
if (authDate.after(f.parse("2020-06-11 00:00:00")) && authDate.before(f.parse("2020-06-20 23:59:59"))) {
date = AuthService.queryAuthInfo("1000011", orderId);
if (null == date) return new AuthInfo("0001", "单号:", orderId, " 状态:待一级审批负责人 ", "段总");
}

return new AuthInfo("0001", "单号:", orderId, " 状态:审批完成");
}

}

  • 这里从上到下分别判断了在指定时间范围内由不同的人员进行审批,就像618上线的时候需要三个负责人都审批才能让系统进行上线。
  • 像是这样的功能看起来很简单的,但是实际的业务中会有很多部门,但如果这样实现就很难进行扩展,并且在改动扩展调整也非常麻烦。

参考:

设计模式之美 (geekbang.org)

[重学 Java 设计模式:实战责任链模式「模拟618电商大促期间,项目上线流程多级负责人审批场景」 | 小傅哥 bugstack 虫洞栈](https://bugstack.cn/md/develop/design-pattern/2020-06-18-重学 Java 设计模式《实战责任链模式》.html)