我们在进行软件开发时要想实现可维护、可扩展,就需要尽量复用代码,并且降低代码的耦合度。设计模式就是一种可以提高代码可复用性、可维护性、可扩展性以及可读性的解决方案。大家熟知的23种设计模式,可以分为创建型模式、结构型模式和行为型模式三大类。本专题着眼于实际开发过程中常用的几种设计模式,从理论和实战两个角度进行讨论和分享,力求逻辑清晰、表述简洁,帮助大家在项目中合理运用设计模式,保障代码的可靠性。
本文为此系列第五篇文章,前四篇见——
第一篇:浅析设计模式1 —— 工厂模式
第二篇:浅析设计模式2 —— 策略模式
第三篇:浅析设计模式3 —— 装饰者模式
第四篇:浅析设计模式4——模板方法模式
概述
我们在进行软件开发时要想实现可维护、可扩展,就需要尽量复用代码,并且降低代码的耦合度,而设计模式就是一种可以提高代码可复用性、可维护性、可扩展性以及可读性的解决方案。
大家熟知的23种设计模式,可以分为创建型模式、结构型模式和行为型模式三大类。其中,行为型模式可用于描述程序中多个类和多个对象如何协作完成复杂的任务,涉及不同对象间的职责分配、算法的抽象化。责任链模式是一种典型的行为型模式,本文将着眼于责任链模式进行学习分享,阐述责任链模式的核心概念和应用实践。认识和使用责任链模式,可以在开发中有效增强应用的灵活性和可扩展性。
基本概念
责任链模式的核心思想是:将请求的每个处理者都视为一个处理节点,再将这些节点连成一条链,当请求到来后便可沿着这条链进行传递,直到有节点处理它为止。这种模式可以有效避免请求发送者和请求接受者之间的耦合关系,用户只需要将请求发送到责任链上,不用关心传递过程和处理细节。
下面从模式结构和使用步骤两个方面,简单阐述责任链方法模式的基本概念。
责任链模式的结构也比较简单易懂,主要包含三大类:抽象处理者类、具体处理者类和客户类,抽象处理者类中首先定义好抽象方法和后继处理机制,具体的处理方法实现将在具体处理者中执行,而客户类将对所有定义的具体处理者组装成链,让请求从链头开始沿着责任链向后执行。
角色 |
关系 |
作用 |
抽象处理者 Handler |
具体处理者的父类 |
定义一个处理请求的接口,包含抽象处理方法和一个后继连接。 |
具体处理者 Concrete Handler |
抽象构件的接口实现类 |
实现抽象处理者的抽象处理方法,判断能否处理本次请求,如果可以则处理,否则将转交给其后继者。 |
客户类 Client |
将具体处理者组装成责任链 |
创建处理链,并向链头的具体处理者提交请求,它不关心处理细节和请求的传递过程。 |
▐ 使用
基于上述基本概念,将装饰者模式的使用步骤概括为:
step1:创建抽象处理者类,定义一个抽象方法和一个指向下一处理者节点的指针 next。
step2:创建具体处理者类,实现抽象处理者类中定义的抽象方法;
step3:创建客户类,将各个具体处理者类组装成责任链,将请求提交给链头的具体处理者类。
使用示例
其实,大家平常接触到的各种审批流,就涉及多个审批节点,不同的审批节点负责人分别持有不同级别的审批权限。比如:请假审批流、紧急发布审批流、报销审批流等等。疫情管控时期,我们学校为了确保在校师生及其他员工的安全,会严格要求学生不能随意出入校门,如果有特殊情况如看病就医,则需在校务系统中进行申请,系统会根据学生请假出校的时长,设置不同级别的负责人进行严格审批。这一过程,用责任链模式就可以实现。
▐ 代码实现
// 创建抽象处理者类,定义指向下一节点的指针和抽象处理方法
public abstract class AbstractHandler {
private AbstractHandler next;
public void setNext(AbstractHandler next) {
this.next = next;
}
public AbstractHandler getNext() {
return next;
}
public abstract void handleRequest(int leaveDayNum);
}
// 定义具体处理者类1:系统自动审批
public class ConcreteHandler1 extends AbstractHandler {
@Override
public void handleRequest(int leaveDayNum) {
if (leaveDayNum <= 1) {
System.out.println("请假不超过" + leaveDayNum + "天" + ": 学校自动审批通过");
} else {
if (getNext() != null) {
getNext().handleRequest(leaveDayNum);
} else {
System.out.println("请假天数过长,需向学院提供签字承诺书");
}
}
}
}
// 定义具体处理中类2:导师审批
public class ConcreteHandler2 extends AbstractHandler {
@Override
public void handleRequest(int leaveDayNum) {
if (leaveDayNum <= 3) {
System.out.println("请假不超过" + leaveDayNum + "天" + ": 导师审批通过");
} else {
if (getNext() != null) {
getNext().handleRequest(leaveDayNum);
} else {
System.out.println("请假天数过长,需向学院提供签字承诺书");
}
}
}
}
// 定义具体处理者类3:辅导员审批
public class ConcreteHandler3 extends AbstractHandler {
@Override
public void handleRequest(int leaveDayNum) {
if (leaveDayNum <= 5) {
System.out.println("请假不超过" + leaveDayNum + "天" + ": 辅导员审批通过");
} else {
if (getNext() != null) {
getNext().handleRequest(leaveDayNum);
} else {
System.out.println("请假天数过长,需向学院提供签字承诺书");
}
}
}
}
// 定义具体处理者类4:院长审批
public class ConcreteHandler4 extends AbstractHandler {
@Override
public void handleRequest(int leaveDayNum) {
if (leaveDayNum <= 7) {
System.out.println("请假不超过" + leaveDayNum + "天" + ": 院长审批通过");
} else {
if (getNext() != null) {
getNext().handleRequest(leaveDayNum);
} else {
System.out.println("请假天数过长,需向学院提供签字承诺书");
}
}
}
}
//定义客户类:组装责任链,向链头节点发送请求
public class chainOfResponsibility {
public static void main(String[] args) {
AbstractHandler handler1 = new ConcreteHandler1();
AbstractHandler handler2 = new ConcreteHandler2();
AbstractHandler handler3 = new ConcreteHandler3();
AbstractHandler handler4 = new ConcreteHandler4();
handler1.setNext(handler2);
handler2.setNext(handler3);
handler3.setNext(handler4);
handler1.handleRequest(6);
}
}
▐ 结果输出
请假不超过6天: 院长审批通过
▐ UML图
扩展
很多文章在介绍责任链模式时都会提到一个概念:纯的责任链模式、不纯的责任链模式,这里也做一个简单的扩展。
纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下一个具体处理者处理。
不纯的职责链模式:允许出现某一个具体处理者在承担了请求的部分责任后,再将剩余责任传给下一个具体处理者继续处理剩余部分责任。
另外,在日常使用中,Handler 不是一定要提供一个设置后继处理器的接口,可以把维护链路的职责独立出来,如下图所示。
在这种责任链模式中,HandlerChain 来维护整条链路,它提供了增删处理者的方法,并实现了抽象处理者类的接口,负责在责任链中传递请求 Request。同时,HandlerChain 内部维护了当前具体处理者在整个责任链中的索引。当客户类发出请求后,HandlerChain 会驱动第一个具体处理者(pos = 0),并根据需要判断是否继续向后传递,若需要则调用 handle() 方法再次驱动下一个具体处理者(pos 在上一次驱动时自增),若不需要则跳出链路;如此反复,直到有具体处理者判断不需再向后传递,或已经执行到最后一个具体处理者。
这样,每个具体处理者不再依赖后继处理器,而是各自在 HandlerChain 中的位置 pos,从而增强模式的灵活性,解除具体处理者之间的依赖性。Servlet 中的过滤器 Filter 就是基于这种方式实现的。
源码赏析
Servlet 约定在请求进入容器后、执行 Servlet.service() 方法前,可以通过 Filter 对 web 资源进行过滤、权限鉴别等处理。在 tomcat 启动时,先加载所有的过滤器信息;在 tomcat 收到请求时,再加载并执行整个过滤器的链路,当请求从链路中脱离后,才会进入真正的业务接口,如下图所示。
在 tomcat 中,每一个 Filter 都是一个具体处理者,不仅能处理请求、还能处理响应。对于 request 来说,责任链结构为 Filter1 -> Filter2 -> Filter3;而对于响应来说,责任链结构为 Filter3 -> Filter2 -> Filter1。这种双向处理的思想其实也很经典,下面将简单分析一下源码。
public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public default void destroy() {}
}
Filter 为抽象处理者,提供了三个方法,其中 doFilter() 方法为处理方法,三个参数分别为请求、响应和链路管理器。接下来看一下链路管理器 FilterChain 的源码。
// FilterChain
public interface FilterChain {
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException;
}
// ApplicationFilterChain
public final class ApplicationFilterChain implements FilterChain {
// 处理器链路,ApplicationFilterConfig 可以理解为对 Filter 的包装
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
// 当前处理器在链路中的索引
private int pos = 0;
// 调用入口
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction() {
@Override
public Void run()
throws ServletException, IOException {
// here
internalDoFilter(req,res);
return null;
}
}
);
} catch( PrivilegedActionException pe) {
Exception e = pe.getException();
if (e instanceof ServletException)
throw (ServletException) e;
else if (e instanceof IOException)
throw (IOException) e;
else if (e instanceof RuntimeException)
throw (RuntimeException) e;
else
throw new ServletException(e.getMessage(), e);
}
} else {
// here
internalDoFilter(request,response);
}
}
// 开始处理
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// 不停的驱动下一个过滤器
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
// 驱动过滤器
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}
// 链路中没有更多过滤器了,开始进入 servlet
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !servletSupportsAsync) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege("service",
servlet,
classTypeUsedInService,
args,
principal);
} else {
// 进入servlet 处理实际业务
servlet.service(request, response);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}
}
ApplicationFilterChain 用数组来组织各处理者的先后顺序,并提供一个当前处理者在链路中的索引 pos 。当链路未在中途断开且当前处理者已是最后一个处理者时,调用 Servlet.service(request, response) 进入业务处理逻辑。作为用户,我们可以通过配置文件或者注入等方式,根据需要定义新的 Filter。
优缺点及适用场景
▐ 优点
降低对象之间的耦合度。一个节点对象无须关心链的结构、到底是哪一个对象处理其请求,发送者和接收者也无须拥有对方的明确信息。
增强系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
灵活地为对象指派职责。当工作流程变化时,可动态改变节点或调动节点次序,也可动态增删节点。
简化节点之间的连接。各节点只需保持一个指向其后继者的引用,避免使用众多 if 或 if…else 语句。
责任分担,符合类的单一职责原则。每个节点类只需处理自己该处理的工作,不该处理的传递给下一个节点类完成,各类的责任范围非常明确。
▐ 缺点
不能保证每个请求一定被处理。一个请求没有明确的接收者,可能一直传到链的末端都得不到处理。
当责任链太长时,一个请求可能需要涉及多个处理者,系统性能会受到一定影响。
责任链建立的合理性需要由客户端来保证,增加了客户端的复杂性,也可能会因为错误设置而导致系统陷入死循环。
▐ 适用场景
在运行时需要动态使用多个关联对象来处理同一次请求时。比如,请假流程、员工入职流程、编译打包发布上线流程等。
不想让使用者知道具体的处理逻辑时。比如,做权限校验的登录拦截器。
需要动态更换处理对象时。比如,工单处理系统、网关 API 过滤规则系统等。
职责链模式常被用在框架开发中,实现过滤器、拦截器等功能,使用者可以在不修改源码的情况下,添加新的过滤拦截功能。
团队介绍
我们是大聚划算技术团队。负责支持聚划算、百亿补贴、天天特卖、淘特价等业务。我们聚焦优惠和选购体验,通过数智化驱动形成更有效率和确定性的货品运营方法论,为消费者提供精选和极致性价比的商品,为商家提供更具爆发确定性的营销方案。
¤ 拓展阅读 ¤
3DXR技术 | 终端技术 | 音视频技术
服务端技术 | 技术质量 | 数据算法