Java设计模式之责任链模式
本文通过图书馆管理系统中,用户名校验、密码校验、需要增加问题,每次都要增加if判断语句,将其改用责任链模式进行链式调用,为了让代码更加的优雅,我们使用之前学过的建造者模式就代码进行改造。接着我们会介绍责任链模式在我们常用的框架中的运用,最后是责任链模式的优缺点和应用场景。
读者可以拉取完整代码到本地进行学习,实现代码均测试通过后上传到码云。
一、引出问题
小王给老王打造了一套图书馆管理系统,随着访问量的不断增加,老王要求增加访问的用户名校验。
小王说这有何难,说着就在用户访问图书馆之前加了一层判断语句,判断用户名是否合法。过了一段时间后,又给每个用户颁发了一个密码,就需要在用户名校验通过以后校验密码。
小王就准备在用户名的判断语句后,增加密码的校验语句。老王赶忙拦住了要继续更改代码的小王。如果以后再增加角色校验、权限校验、你准备写多少个判断语句。
而且你把软件设计原则中的——开闭原则丢到哪里去了。
你可以考虑使用一种模式,将所有的校验方法都独立出来一个类,每一个类只负责处理各自的校验逻辑,当前的校验类通过以后传递给下一个校验类进行处理,这样每次增加新的逻辑判断都只需要增加校验类就行了。
就像是一条流水线,每个类负责处理线上的一个环节。
二、责任链模式的概念和使用
实际上,老王提出来的正是行为型设计模式中的——**责任链模式。
责任链模式正如它的名字一样,将每个职责的步骤串联起来执行,并且一个步骤执行完成之后才能够执行下一个步骤。
从名字可以看出通常责任链模式使用链表来完成。 因此当执行任务的请求发起时,从责任链上第一步开始往下传递,直到最后一个步骤完成。
在责任链模式当中, 客户端只用执行一次流程开始的请求便不再需要参与到流程执行当中,责任链上的流程便能够自己一直往下执行,
客户端同样也并不关心执行流程细节,从而实现与流程之间的解耦。
责任链模式需要有两个角色:
抽象处理器(Handler):处理器抽象接口,定义了处理请求的方法和执行下一步处理的处理器。
具体处理器(ConcreteHandler):执行请求的具体实现,先根据请求执行处理逻辑,完成之后将请求交给下一个处理器执行。
基于责任链模式实现图书馆的用户名校验和密码校验。
抽象处理器:
/** * @author tcy * @Date 22-08-2022 */ public abstract class Handler { private Handler next; public Handler getNext() { return next; } public void setNext(Handler next) { this.next = next; } public abstract void handle(Object request); }
用户名校验处理器:
/** * @author tcy * @Date 23-08-2022 */ public class ConcreteHandlerUsername extends Handler{ @Override public void handle(Object request) { //相应的业务逻辑... System.out.println("用户名校验通过. 参数: " + request); //调用链路中下一个节点的处理方法 if (getNext() != null) { getNext().handle(request); } } }
密码校验器:
/** * @author tcy * @Date 23-08-2022 */ public class ConcreteHandlerPassword extends Handler{ @Override public void handle(Object request) { //相应的业务逻辑... System.out.println("密码校验通过. 参数: " + request); //调用链路中下一个节点的处理方法 if (getNext() != null){ getNext().handle(request); } } }
客户端调用:
public class Client { //普通模式---------- public static void main(String[] args) { Handler concreteHandler1 = new ConcreteHandlerUsername(); Handler concreteHandler2 = new ConcreteHandlerPassword(); concreteHandler1.setNext(concreteHandler2); concreteHandler1.handle("用户名tcy"); } }
用户名校验通过. 参数: 用户名tcy
密码校验通过. 参数: 用户名tcy
这样我们就实现了责任链模式,但是这种方式我们注意到,调用方调用的时候手动将两个处理器set到一起,如果这条链路很长的时候,这样的代码实在是太不优雅了。
将我们曾经学过的设计模式扒出来,看使用哪种模式能让它看起来更优雅一点。
三、责任链模式+建造者模式
我们看建造型设计模式的文章,看建造者模式中的典型应用中的Lombok。
参考Lombok的 @Builder例子,是不是和我们这个有着些许相似呢?
我们在Handle的类中创建一个Builder内部类。
/** * 建造者模式 */ public static class Builder{ private Handler head; private Handler tail; public Builder addHanlder(Handler handler){ //head==null表示第一次添加到队列 if (null == head){ head = this.tail = handler; return this; } //原tail节点指向新添加进来的节点 this.tail.setNext(handler); //新添加进来的节点设置为tail节点 this.tail = handler; return this; } public Handler build(){ return this.head; } }
该内部类更像是一个链表结构,定义一个头和尾对象,add方法是向链接的头尾中赋值,build返回头元素方便开始链式调用。我们对调用方代码进行改造。
//建造者模式--------- public static void main(String[] args) { Handler.Builder builder = new Handler.Builder(); builder.addHanlder(new ConcreteHandlerUsername()) .addHanlder(new ConcreteHandlerPassword()); builder.build().handle("用户名tcy"); }
这样的实现方式比原方式优雅多了。责任链模式本身是很简单的,如果将责任链模式和建造者模式组合起来使用就没那么容易理解了。
在实际使用中往往不是一个单一的设计模式,更多的是多种组合模式组成的“四不像”,实际上这并不是一件轻松的事。
四、责任链模式在源码运用
为了加深理解我们继续深入责任链模式在Spring中的运用。
Spring Web 中的 HandlerInterceptor
,里面有preHandle()
、postHandle()
、afterCompletion()
三个方法,实现这三个方法可以分别在调用"Controller"方法之前,调用"Controller"方法之后渲染"ModelAndView"之前,以及渲染"ModelAndView"之后执行。
public interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
HandlerInterceptor
就是角色中的抽象处理者,HandlerExecutionChain相当于上述中的Client,用于调用责任链上的各个环节。
public class HandlerExecutionChain { ... @Nullable private HandlerInterceptor[] interceptors; private int interceptorIndex = -1; 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; } this.interceptorIndex = i; } } return true; } void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable 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); } } } void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable 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); } } } } }
私有的数组 private HandlerInterceptor[] interceptors 用于存储责任链的每个环节,,然后通过interceptorIndex
作为指针去遍历责任链数组按顺序调用处理者。
结合我们上面给出的例子,在Spring中的应用是比较容易理解的。
在Servlet的一系列拦截器也是采用的责任链模式,有兴趣的读者可以深入研究一下。
五、总结
当必须按顺序执行多个处理者时,可以考虑使用责任链模式。如果处理者的顺序及其必须在运行时改变时,可以考虑使用责任链模式。责任链的模式是缺点也很明显,增加了系统的复杂性。
但是要切忌避免过度设计,在实际应用中,校验用户名和密码的业务逻辑并没有那么的复杂,可能只是一个判断语句,使用设计模式只会增加系统的复杂性,而在Shiro、SpringSecurity、SpringMVC的拦截器中使用责任链模式是一个好的选择。
如果在你的项目业务中需要定义一系列拦截器,那么使用责任链模式就是一个比较不错的选择。
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对猪先飞的支持。如果你想了解更多相关内容请查看下面相关链接
原文出处:https://www.cnblogs.com/tianClassmate/p/16618998.html
相关文章
- 这篇文章主要介绍了如何利用java语言实现经典《复杂迷宫》游戏,文中采用了swing技术进行了界面化处理,感兴趣的小伙伴可以动手试一试...2022-02-01
java 运行报错has been compiled by a more recent version of the Java Runtime
java 运行报错has been compiled by a more recent version of the Java Runtime (class file version 54.0)...2021-04-01- 今天小编在这里就来给各位photoshop的这一款软件的使用者们来说一说设计一幅大鱼海棠动画片海报制作的实例教程,各位想知道具体制作步骤的使用者们,那么各位就快来看看...2016-09-14
- 这篇文章主要介绍了在java中获取List集合中最大的日期时间操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
- 这篇文章主要介绍了教你怎么用Java获取国家法定节假日,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下...2021-04-23
- 这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-31
- 说起C#和Java这两门语言(语法,数据类型 等),个人以为,大概有90%以上的相似,甚至可以认为几乎一样。但是在工作中,我也发现了一些细微的差别...2020-06-25
- 这篇文章主要介绍了Postgresl 如何选择正确的关闭模式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-18
- 神马是“解释器模式”?先翻开《GOF》看看Definition:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。在开篇之前还是要科普几个概念: 抽象语法树: 解释器模式并未解释如...2014-06-07
- ps软件是一款非常不错的图片处理软件,有着非常不错的使用效果。这次文章要给大家介绍的是ps怎么制作倒影,一起来看看设计倒影的方法。 用ps怎么做倒影最终效果̳...2017-07-06
- 这篇文章主要介绍了解决Java处理HTTP请求超时的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-29
- 这篇文章主要介绍了C语言程序设计第五版谭浩强课后答案(第二章答案),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2021-04-02
- 这篇文章主要介绍了java 判断两个时间段是否重叠的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
- 这篇文章主要介绍了超简洁java实现双色球若干注随机号码生成(实例代码),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-02
- 今天小编在这里就来给Photoshop的这一款软件的使用者们来说下计商务名片的5种常见思路,各位想知道的使用者,那么下面就快来跟着小编一起看一看吧。 给各位Photosho...2016-09-14
java 画pdf用itext调整表格宽度、自定义各个列宽的方法
这篇文章主要介绍了java 画pdf用itext调整表格宽度、自定义各个列宽的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-31- 这篇文章主要介绍了Java生成随机姓名、性别和年龄的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-01
- 这篇文章主要为大家介绍了JavaScript设计模式中的装饰者模式,对JavaScript设计模式感兴趣的小伙伴们可以参考一下...2016-01-21
- 很多集成的PHP环境(PHPnow WAMP Appserv等)自带的MySQL貌似都没有开启MySQL的严格模式,何为MySQL的严格模式,简单来说就是MySQL自身对数据进行严格的校验(格式、长度、类型等),比如一个整型字段我们写入一个字符串类型的数...2013-10-04
- 这篇文章主要介绍了java正则表达式判断前端参数修改表中另一个字段的值,需要的朋友可以参考下...2021-05-07