设计模式[15]——从审批流程中学习责任链模型。在责任链模式中,许多对象通过每个对象对其下一个家的引用连接起来形成一个链。请求在链上传递,直到链上的对象决定处理该请求。发出这个请求的客户端并不知道链上的哪个对象最终会处理这个请求,这使得系统可以在不影响客户端的情况下动态重组和分配职责。(百度百科)责任链模式是一种行为设计模式,即专注于处理数据。假设我们有一个数据需要经过很多节点处理,那么它会是这样的:一个节点处理完之后,交接给下一个节点,不知道大家有没有用过审批流程。我们提交审批表后,你们的领导会审批。领导批准后,由主任批复。主管之后可能是高级主管,或cto或hr。他们在同一条链上,如果你的leader没有完成审批,后面的节点是不可能收到信息的。如果你的leader拒绝了你的申请,数据就不会到达后续的审批节点。如果你接触过前端,在JS中点击某个div时,会产生冒泡事件,即点击下方A,A在B中,B在C中,A->B->C会依次接收点击事件:再举个例子,在SpringMVC中,我们有时会定义一些拦截器来对请求进行预处理,即当请求到来时,会依次经过拦截器,之后进入我们的处理业务逻辑传递拦截器代码。以前在做人事管理的时候,有一个涉及到人事离职的流程,比如交接工作、撤销权限、注销账号等等,这整个流程非常适合责任链来处理。当然,自动处理过程会出错,保存每个阶段的状态,对于出错的场景,可以手动从责任链断开的地方继续执行。这整个过程的框架就是责任链的应用,但是根据实际场景又增加了很多其他的东西。两个问题:责任链中的每个节点是否都必须包含对下一个节点的引用?答:不一定,要么把所有负责的节点都放在一个列表里,依次处理;或者每个节点都包含对下一个负责节点的引用。责任链是不允许打断还是不允许打断?答:两者都可以,具体情况可以根据自己的场景使用。ChainofResponsibility模式中的角色责任链一般有以下几个角色:Client(客户端):调用责任链处理器的处理方法,或者调用第一个链对象中的handle方法。Handler(处理器):一个抽象类,提供给实际的处理器继承然后实现handle方法来处理请求的ConcreteHandler。HandlerChain:负责组合责任链的所有节点和进程(如果节点包含对下一个节点的引用,那么HandlerChain可能不存在)。审批链的实现让我们分别实现不同的写法。假设有一个场景,秦淮入职了。一家公司工作一年了,工资一直没有涨。再过一年,工资就得涨了。申请表:【加薪申请】不间断模式先演示不间断模式,首先要得到申请表的实体,里面包含申请表的名字和求职者:publicclassRequisition{//name公共字符串名称;//申请人publicString申请人;publicRequisition(Stringname,Stringapplicant){this.name=name;this.applicant=申请人;}}责任链中的每个责任节点,也就是处理器,可以抽象成一个接口:publicinterfaceHandler{//处理申请表voidprocess(Requisitionrequisition);}我们依次实现了三个不同的责任节点,分别代表leader、director、hr审批:publicclassManagerHandlerimplementsHandler{@Overridepublicvoidprocess(Requisitionrequisition){System.out.println(String.format("Managerapprovestheapplicationform[%s]from[%s]...",requisition.applicant,requisition.name));}}publicclassDirectorHandlerimplementsHandler{@Overridepublicvoidprocess(Requisitionrequisition){System.out.println(String.format("主任批准了[%s]的申请表[%s]...",requisition.applicant,requisition.name));}}publicclassHrHandlerimplementsHandler{@Overridepublicvoidprocess(Requisitionrequisition){System.out.println(String.format("Hr从[%s]批准申请表[%s]...",requisition.申请人,申请.name));}}责任节点可用,我们需要将它们与责任链结合起来:publicclassHandlerChain{Listhandlers=newArrayList<>();publicvoidaddHandler(Handlerhandler){handlers.add(handler);}publicvoidhandle(Requisitionrequisition){for(Handlerhandler:handlers){handler.process(requisition);}System.out.println(String.format("来自[%s]的申请表[%s]已获批准",requisition.applicant,requisition.name));}}客户端测试类:publicclassClientTest{publicstaticvoidmain(String[]args){HandlerChainhandlerChain=newHandlerChain();handlerChain.addHandler(新的ManagerHandler());handlerChain.addHandler(newDirectorHandler());handlerChain.addHandler(newHrHandler());handlerChain.handle(newRequisition("加薪申请","秦淮"));}}运行结果:经理批准【秦淮】申请表【加薪申请】...总监批准申请表【秦淮】【加薪申请】...hr批准申请表【秦淮】【加薪申请】……【秦淮】的申请表【加薪申请】已经通过了。从结果来看,申请表确实走遍了每一个节点,形成了一个链条。这就是责任链的核心思想。是一样的数据,一样的申请表。打断模式秦淮提薪的想法很好,但现实很骨感。上面的审批过程一路顺风顺水,但是万一HR要拒绝这个申请表,上面的代码并没有给她这个能力。因此,代码必须改变!(hr的心:我要这个功能,明天上线)。由于支持中断,即支持任何一个节点审批不通过直接返回,不会去下一个节点。首先给抽象处理节点方法添加一个返回值:publicinterfaceHandler{//处理申请表booleanprocess(Requisitionrequisition);}三个处理节点也同步修改:publicclassManagerHandlerimplementsHandler{@Overridepublicbooleanprocess(Requisitionrequisition){System.out.println(String.format("Managerapprovalfrom[%s]Requisition[%s]...",requisition.applicant,requisition.name));返回真;}}publicclassDirectorHandlerimplementsHandler{@Overridepublicbooleanprocess(Requisitionrequisition){System.out.println(String.format("Directorapprovedtheapplicationform[%s]from[%s]...",requisition.申请人,申请.name));返回真;}}publicclassHrHandlerimplementsHandler{@Overridepublicbooleanprocess(Requisitionrequisition){System.out.println(String.format("Hr未能批准来自[%s]的申请表[%s]...",requisition.applicant,requisition.name));返回假;}}流程链调整:publicclassHandlerChain{Listhandlers=newArrayList<>();publicvoidaddHandler(Handlerhandler){handlers.add(handler);}publicvoidhandle(Requisitionrequisition){for(Handlerhandler:handlers){if(!handler.process(requisition)){System.out.println(String.format("Requisitionform[%s]from[%s]审批失败",requisition.applicant,requisition.name));返回;}}System.out.println(String.format("来自[%s]的申请表[%s]已获批准",requisition.applicant,requisition.name));}}修改完成后的结果:经理批准了【秦淮】的申请表【加薪申请】...主任批准了申请表【秦淮】的申请表【加薪申请】...hr没批【秦淮】的申请表【加薪申请】】……【秦淮】的申请表【加薪申请】没批秦淮哭了,加薪审批被拒了byhr虽然被拒绝了,但是秦淮也感受到了责任链模型是可以打断的,这种写法在处理请求的时候也是比较常见的,因为我们不希望非法的请求被包含在正常的处理逻辑中.包括对下一个节点的引用如前所述,在责任链模式中,许多对象通过每个对象对其下一个节点的引用连接起来形成一个链。以上写法不包括下节点引用。让我们练习一下如何使用引用编写的方法来完成责任链。将Handler接口转化为抽象类:publicabstractclassHandler{privateHandlernextHandler;publicvoidsetNextHandler(Handlerhandler){this.nextHandler=handler;}//处理申请表protectedabstractbooleanprocess(Requisitionrequisition);//公开的方法publicbooleanhandle(Requisitionrequisition){booleanresult=process(requisition);如果(结果){如果(nextHandler!=null){返回nextHandler.handle(申请);}else{返回真;}}返回假;}}三实现类保持不变:publicclassManagerHandlerextendsHandler{@Overridebooleanprocess(Requisitionrequisition){System.out.println(String.format("经理批准了来自[%s]的申请表[%s]。..",requisition.applicant,requisition.name));返回真;}}publicclassDirectorHandlerextendsHandler{@Overridepublicbooleanprocess(Requisitionrequisition){System.out.println(String.format("主任批准了来自[%s]的申请表[%s]...",requisition.applicant,requisition.name));返回真;}}publicclassHrHandlerextendsHandler{@Overridepublicbooleanprocess(Requisitionrequisition){System.out.println(String.format("Hr未能批准来自[%s]的申请表[%s]...",requisition.applicant,requisition.name));返回假;}}测试方法,构造嵌套引用:publicclassClientTest{publicstaticvoidmain(String[]args){HrHandlerhrHandler=newHrHandler();DirectorHandlerdirectorHandler=newDirectorHandler();导演处理程序。setNextHandler(hrHandler);ManagerHandlermanagerHandler=newManagerHandler();managerHandler.setNextHandler(directorHandler);managerHandler.handle(newRequisition("加薪申请","秦淮"));}}可以看到运行结果是一样的:经理批准了[秦淮]的申请表[加薪申请]...主任批准了[秦淮]的申请表[加薪申请]...hr审批失败【秦淮】的申请表【加薪申请】……展开。事实上,责任链在Spring中更有用。主要有两点:1、可以通过注入自动识别接口@AutowirepublicListhandlers的所有实现类;2.可以使用@Order注解让接口实现类按顺序执行。@Order(1)publicclassHrHandlerextendsHandler{...}Mybatis中的Plugin机制在源码中采用责任链模式,配置各种官方或自定义的Plugins。类似于Filter,它可以在执行Sql语句的时候执行一些动作。Spring使用责任链模式来管理顾问。比如Mybatis可以添加几个插件,比如PageHelper,多个插件使用动态代理封装对象,多层代理。//责任链插件publicclassInterceptorChain{privatefinalListinterceptors=newArrayList<>();//生成代理对象publicObjectpluginAll(Objecttarget){for(Interceptorinterceptor:interceptors){target=interceptor.plugin(target);}返回目标;}//逐层拦截publicvoidaddInterceptor(Interceptorinterceptor){interceptors.add(interceptor);}publicListgetInterceptors(){returnCollections.unmodifiableList(interceptors);}}总结一下责任链模式的优点:减少对象的直接耦合,无论是引用方法还是非引用方法,对象都会自动传递给下一个责任节点。为了增强可扩展性,如果需要增加新的责任节点,也比较方便,只需要实现特定的接口即可。负责节点的顺序是可控的,可以指定一个sequence属性,排序即可。每个职责节点都有特定的职责,只处理自己的任务,符合类的单一职责原则。责任链的缺点:如果责任链比较长,会影响性能。责任链可能中途断裂,请求可能不被接受。责任链一般是以流程为基础的处理方式。多个节点处理同一块数据并顺序传输它们。可能有也可能没有顺序要求。处理器的能力被抽象成接口,便于扩展。设计模式系列:设计模式【1】--单例模式有几种写法?设计模式【1.1】——你想如何打破单例模式?设计模式【1.2】--枚举单例这么好用?设计模式[1.3]--Hungry式单例为什么是线程安全的?设计模式[2]--看简单工厂模式?设计模式[2.1]--简单工厂模式是如何演变成工厂方法模式的?设计模式[2.2]--工厂模式是如何演变成抽象工厂模式的?设计模式[3.1]--浅谈代理模式之静态、动态、cglib代理设计模式[3.2]--JDK动态代理源码解析有多香?设计模式[3.3]--CGLIB动态代理源码解释设计模式[4]--建造者模式设计模式详解[5]--原型模式设计模式[6.1]--适配器模式设计模式初探[6.2]]--浅谈适配器模式设计模式[7]--探究桥接模式设计模式[8]--手把手教我写装饰器模式设计模式[9]--外观模式?没那么高大上的设计模式[10]——顺便看看享元模式设计模式[11]——搞定组合模式设计模式[12]——搞定最近流行的策略模式设计模式[13]--模板模式胡同怎么样?DesignPatterns[14]--LearningCommandPatternsfromSmartSpeakers【作者简介】:公众号【秦淮杂货店】作者秦淮,个人网站:http://aphysia.cn,技术之路是不是从前,山高水长,纵然慢也不会停。剑指全题OfferPDF开源编程笔记