俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及的总结知识点如下:
前面说了一个状态模式,总结过程中发现和这个责任链的使用场景很类似,都是为了解耦大量复杂业务逻辑判断的,那么他们有什么不同呢?回忆状态模式——状态模式允许通过改变对象的内部状态而改变对象自身的行为,这个对象表现得就好像修改了它的类一样。
在之前也说了,状态模式关键是各个状态子类必须知道下一个状态是啥,且要把逻辑判断转移到各个状态子类中,客户端不需要了解状态迁移的顺序,且状态模式虽然类图还尼玛和策略模式几乎一样,但是策略目的是针对单一算法的运行时替换,客户端需要事先了解策略,主动去选择合适的策略,不存在状态的自动迁移!
在看责任链,Chain of Responsibility(CoR)——责任链模式,也叫职责链模式或者职责连锁模式,同状态模式一样,也是对象的行为模式之一,该模式构造一系列分别担当不同的职责的类的对象来共同完成一个任务,对象由每一个对象对其下家的引用而连接起来形成一条链,这些类的对象之间像链条一样紧密相连,而客户端发出的请求在这个链上传递,直到链上的某一个对象决定处理此请求,发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任,所以该模式被称作职责链模式。
它的特点是各个职责类(类比状态模式的状态类们)职责单一不彼此依赖,且职责自动转移,但是和状态模式不同的是,责任链模式的责任类不知道自己的下一个需要转移到的职责是哪个,等价于——发出完成某任务请求的客户端并不知道链上的哪一个对象最终处理这个请求,这个组装过程需要交给环境类去完成,所以非常灵活!
比如客户Client要完成一个任务,这个任务包括a,b,c,d四个部分,首先客户Client把任务交给A,A完成a部分之后,把任务交给B,B完成b部分……直到D完成d部分。再看,政府部分的某项工作,县政府先完成自己能处理的部分,不能处理的部分交给省政府,省政府再完成自己职责范围内的部分,不能处理的部分交给中央政府,中央政府最后完成该项工作。还有,软件窗口的消息传播……但是以上的责任的转移,或者说在责任链上的移动,各个责任类不知道具体顺序和下一个责任,链条的组装过程是环境类(或客户端完成的)。如图:
写一个例子,汽配厂组装汽车,有车身,车尾,车头……现在需要一条生产线组装汽车,代码实现:
1 public abstract class CarController { 2 /** 3 * 控制组装车的组装过程 4 */ 5 public abstract void ControlCar(); 6 } 7 8 public class CarHead extends CarController { 9 /** 10 * 具体的组装责任(任务) 11 */ 12 @Override 13 public void ControlCar() { 14 System.out.println("组装汽车的头部"); 15 } 16 } 17 18 public class CarBody extends CarController { 19 @Override 20 public void ControlCar() { 21 System.out.println("组装汽车的身体"); 22 } 23 } 24 25 public class CarTail extends CarController { 26 @Override 27 public void ControlCar() { 28 System.out.println("组装汽车的尾部"); 29 } 30 } 31 32 public class MainClass { 33 34 public static void main(String[] args) { 35 // 进行组装 36 CarController head = new CarHead(); 37 CarController body = new CarBody(); 38 CarController tail = new CarTail(); 39 40 // 手动的实现组装过程 41 head.ControlCar(); 42 body.ControlCar(); 43 tail.ControlCar(); 44 } 45 }
貌似完成任务了,但是这样搞有问题,
第一:保不齐以后组装技术提高了,生产线上的组装顺序会变化,或者 多/少 几个组装部分。此时修改代码,不仅职责类需要修改,客户端也需要修改。违反了开闭原则。
第二:组装过程是很low的,完全没有实现自动组装!而是每次都要手动进行各个部件的组装。其实我只需要给生产线下一个指令,一个事先设计的组装流程就ok了,剩下的让生产线全自动的运行!
又多了个汽车美容,和宣传功能,现在优化代码:
public abstract class CarControllerB { /** * 以后各个责任类要持有的下一个对象引用 */ protected CarControllerB successor; public CarControllerB getSuccessor() { return successor; } public void setSuccessor(CarControllerB successor) { this.successor = successor; } /** * 控制组装车的组装过程 */ public abstract void ControlCar(); } public class CarHeadB extends CarControllerB { /** * 具体的组装责任(任务) */ @Override public void ControlCar() { System.out.println("组装汽车的头部"); if (getSuccessor() != null) { getSuccessor().ControlCar(); } } } public class CarBodyB extends CarControllerB { @Override public void ControlCar() { System.out.println("组装汽车的身体"); if (getSuccessor() != null) { getSuccessor().ControlCar(); } } } public class CarTailB extends CarControllerB { @Override public void ControlCar() { System.out.println("组装汽车的尾部"); if (getSuccessor() != null) { getSuccessor().ControlCar(); } } } public class CarDrumbeating extends CarControllerB { @Override public void ControlCar() { System.out.println("进行宣传工作"); if (getSuccessor() != null) { getSuccessor().ControlCar(); } } } public class CarCosmetology extends CarControllerB { @Override public void ControlCar() { System.out.println("给车美容"); if (getSuccessor() != null) { getSuccessor().ControlCar(); } } }
记住,持有下一个责任类的对象因为必须不能是private的,否则无法被扩展!下面编写环境类
public class Client { /** * 在环境类(客户端)里按照业务需要,动态的组装各个职责类为一条链条 */ public void execute() { CarControllerB head = new CarHeadB(); CarControllerB body = new CarBodyB(); CarControllerB tail = new CarTailB(); CarControllerB drumbeating = new CarDrumbeating(); CarControllerB cosmetology = new CarCosmetology(); // 灵活的组装生产线的顺序,目前规定,先组装头,之后尾部,最后身子!完成之后美容,宣传出去! head.setSuccessor(tail); tail.setSuccessor(body); body.setSuccessor(cosmetology); cosmetology.setSuccessor(drumbeating); // 自动的开启生产线,调用链条头部 head.ControlCar(); } }
客户端调用
public class MainClassB { public static void main(String[] args) { Client client = new Client(); client.execute(); } }
结果:
组装汽车的头部
组装汽车的尾部
组装汽车的身体
给车美容
进行宣传工作
后来,生成任务有变化,需要改变组装顺序,不宣传了,先放一放。那么直接在环境里修改链接的顺序,客户端不需要改变(甚至对客户端clinet,可以抽象出一个接口,每个组装链条都作为一个子类去实现该接口,我发现这里又有了策略模式的影子)
/** * 在环境类(客户端)里按照业务需要,动态的组装各个职责类为一条链条 */ public void execute() { CarControllerB head = new CarHeadB(); CarControllerB body = new CarBodyB(); CarControllerB tail = new CarTailB(); CarControllerB drumbeating = new CarDrumbeating(); CarControllerB cosmetology = new CarCosmetology(); // 灵活的组装生产线的顺序,目前规定,先组装头,之后尾部,最后身子!完成之后美容,宣传出去! head.setSuccessor(tail); tail.setSuccessor(body); body.setSuccessor(cosmetology); // 自动的开启生产线,调用链条头部 head.ControlCar(); }
组装汽车的头部
组装汽车的尾部
组装汽车的身体
给车美容
我曾经被人问过,每次都尼玛set一下写一行,set一下写一行,代码量不少啊,也麻烦!咋办,其实任何模式都不是一成不变的,还是那句话,设计模式最初于GoF提出,源码是c++,也就是说,不要纠结具体的代码实现,不要纠结具体的类图,模式是一种思想,面向对象编程思想的体现。我这样做,这是之前的抽象处理类(接口);
public abstract class CarControllerB { /** * 以后各个责任类要持有的下一个对象引用 */ protected CarControllerB successor; public CarControllerB getSuccessor() { return successor; } public void setSuccessor(CarControllerB successor) { this.successor = successor; } /** * 控制组装车的组装过程 */ public abstract void ControlCar(); }
改进之后:
public abstract class CarControllerB { /** * 以后各个责任类要持有的下一个对象引用 */ protected CarControllerB successor; public CarControllerB getSuccessor() { return successor; } public CarControllerB setSuccessor(CarControllerB successor) { this.successor = successor; return this.successor; } /** * 控制组装车的组装过程 */ public abstract void ControlCar(); }
当然之前的代码还是可以用的,只不过client类变的更加简单了为:
public class Client { /** * 在环境类(客户端)里按照业务需要,动态的组装各个职责类为一条链条 */ public void execute() { CarControllerB head = new CarHeadB(); CarControllerB body = new CarBodyB(); CarControllerB tail = new CarTailB(); CarControllerB drumbeating = new CarDrumbeating(); CarControllerB cosmetology = new CarCosmetology(); // 灵活的组装生产线的顺序,目前规定,先组装头,之后尾部,最后身子!完成之后美容,宣传出去! head.setSuccessor(tail).setSuccessor(body).setSuccessor(cosmetology).setSuccessor(drumbeating); // 自动的开启生产线,调用链条头部 head.ControlCar(); } }
注意,该例子比较简单,具体任务都是用打印实现的,一会儿介绍一个servlet的过滤器。
要实现Chain of Responsibility模式,需要满足的基本条件是什么?
说说责任链模式的各个角色,画出责任链模式的类图
●抽象处理者(Handler)角色:定义出一个处理请求的抽象接口。如果需要接口可以定义出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。
●具体处理者(ConcreteHandler)角色:具体处理者作为子类去继承(实现)抽象处理角色,当他们接到请求后,可以选择将请求处理掉,或者忽略而将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
说说责任链模式和状态模式的区别
不可否认,状态模式也好,责任链模式也罢,都能解耦和优化大量的逻辑判断……
责任链模式使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象练成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。各个责任类不知道也没必要知道下一个责任对象是谁!由环境类统一设置顺序和谁连接到链条,谁不连接到链条……从代码中我们可以看出,职责链在client(环境类)连接,也就是说,如果我们的生产线一旦改变,比如说我们不需要美容了,我们需要增加新的组装项目了,或者是先组装车头后,直接请求去保存到仓库……这都是很容易实现的,职责链模式要比状态模式灵活很多。
但是,这时候有人要问,既然他们都可以解决逻辑判断的分支过多的问题,那么,是不是责任链模式比状态模式好呢?
职责链模式过于灵活,在客户端使用时,需要环境去确定下一个对象是谁,一些列的set操作……在多次设置的时候很容易出问题,而且状态模式是一个对象的内在状态发生改变(一个对象,相对比较稳定,处理完一个对象下一个对象的处理一般都已确定),而职责链模式是多个对象之间的改变(多个对象之间的话,就会出现某个对象不存在的情景,就像之前讲状态模式时的公司请假系统,可能存在不同级别,不同类型员工请假流程不一样,此时用状态模式不太好),这也说明他们两个模式处理的情况不同。
其实,这两个设计模式最大的区别就是
针对具体业务,有人用状态模式,从头到尾提前定义好下一个处理的对象,有人采用责任链,随时都有可能调整链的顺序……甚至不复杂的业务判断,或者只需要使用一次的情景下,那就没必要搞这些鸡毛模式,本着够用原则和具体业务的适合原则!
责任链模式有什么优缺点?
优点:
缺点:因为处理时以链的形式在对象间传递消息,根据实现方式不同,有可能会影响处理的速度,增加代码量,同之前的道理,为了提高灵活性,会牺牲代码量!
责任链模式的分类:纯的责任链和不纯的责任链
作为一个补充知识,非重点。
纯的责任链模式要求一个具体的处理者对象只能在两个行为中选择一个:一是承担责任,二是把责任推给下家。不允许出现某一个具体处理者对象在承担了一部分责任后又把该责任向下传的情况。在一个纯的责任链里,一个请求必须被某一个处理者对象完全所接收。
不纯的责任链模式,一个请求可以最终不被任何接收端对象所接收。纯的责任链模式的实际例子很难找,一般看到的例子均是不纯的责任链模式实现。在实际的系统里,纯的责任链很难找到。如果认为责任链不纯便不是责任链模式,那么责任链模式便不会有太大意义了。
JDK中使用责任链的例子:javax.servlet.Filter#doFilter()——servlet的过滤器
在Web应用里,过滤器位于客户端和Web应用程序之间,用于检查和修改两者之间流过的请求和响应数据,在请求到达Servlet/JSP之前,过滤器截获请求(拦截器),之后在进行实际业务的处理,处理完毕最后的响应返回给客户端之前,过滤器再次截获响应进行一些操作(检验等)。多个过滤器形成一个过滤器链,过滤器链中不同过滤器的先后顺序由部署文件web.xml中过滤器映射<filter-mapping>的顺序决定。最先截获客户端请求的过滤器将最后截获Servlet/JSP的响应信息。当然了,servlet的过滤器还使用了比如装饰模式,以后总结。
servlet过滤器经典案例
下面简单看一个小例子,测试类需要实现javax.servlet.Filter#doFilter()接口,demo例子
1 public class FilterDemo implements Filter { 2 /** 3 * 容器执行,完成过滤器初始化工作 4 * 5 * @param filterConfig FilterConfig 6 * @throws ServletException 7 */ 8 @Override 9 public void init(FilterConfig filterConfig) throws ServletException { 10 System.out.println("过滤器初始化完毕"); 11 } 12 13 /** 14 * 容器调用,每次请求前,响应前,都要经过该方法去过滤 15 * 16 * @param servletRequest ServletRequest 17 * @param servletResponse ServletResponse 18 * @param filterChain FilterChain 19 * @throws IOException 20 * @throws ServletException 21 */ 22 @Override 23 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 24 System.out.println("过滤器执行了!"); 25 // 让下一个资源执行 26 filterChain.doFilter(servletRequest, servletResponse); 27 } 28 29 /** 30 * 容器执行,完成过滤器销毁工作 31 */ 32 @Override 33 public void destroy() { 34 System.out.println("过滤器销毁完毕"); 35 } 36 }
配置web.xml,指定哪些资源需要拦截
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <filter> <filter-name>FilterDemo</filter-name> <filter-class>com.dashuai.servlet1.FilterDemo</filter-class> </filter> <filter-mapping> <filter-name>FilterDemo</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
执行过滤器的结果:
信息: Server startup in 35 ms [2016-02-06 03:24:30,849] Artifact demo1:war exploded: Artifact is being deployed, please wait... Connected to server 二月 06, 2016 3:24:30 下午 org.apache.catalina.deploy.WebXml setVersion 警告: Unknown version string [3.1]. Default version will be used. 过滤器初始化完毕 [2016-02-06 03:24:31,125] Artifact demo1:war exploded: Artifact is deployed successfully [2016-02-06 03:24:31,126] Artifact demo1:war exploded: Deploy took 276 milliseconds 过滤器执行了! 过滤器执行了! 过滤器执行了! 过滤器执行了! 二月 06, 2016 3:24:40 下午 org.apache.catalina.startup.HostConfig deployDirectory 信息: Deploying web application directory D:\apache-tomcat-7.0.67\webapps\manager 二月 06, 2016 3:24:40 下午 org.apache.catalina.startup.HostConfig deployDirectory 信息: Deployment of web application directory D:\apache-tomcat-7.0.67\webapps\manager has finished in 68 ms D:\apache-tomcat-7.0.67\bin\catalina.bat stop Using CATALINA_BASE: "C:\Users\Administrator\.IntelliJIdea14\system\tomcat\Tomcat_7_0_67_jspservlet1" Using CATALINA_HOME: "D:\apache-tomcat-7.0.67" Using CATALINA_TMPDIR: "D:\apache-tomcat-7.0.67\temp" Using JRE_HOME: "D:\Java\jdk1.8.0_60" Using CLASSPATH: "D:\apache-tomcat-7.0.67\bin\bootstrap.jar;D:\apache-tomcat-7.0.67\bin\tomcat-juli.jar" 二月 06, 2016 3:24:46 下午 org.apache.catalina.core.StandardServer await 信息: A valid shutdown command was received via the shutdown port. Stopping the Server instance. 二月 06, 2016 3:24:46 下午 org.apache.coyote.AbstractProtocol pause 信息: Pausing ProtocolHandler ["http-apr-8888"] 二月 06, 2016 3:24:46 下午 org.apache.coyote.AbstractProtocol pause 信息: Pausing ProtocolHandler ["ajp-apr-21963"] 二月 06, 2016 3:24:46 下午 org.apache.catalina.core.StandardService stopInternal 信息: Stopping service Catalina 过滤器销毁完毕 二月 06, 2016 3:24:46 下午 org.apache.coyote.AbstractProtocol stop 信息: Stopping ProtocolHandler ["http-apr-8888"] 二月 06, 2016 3:24:46 下午 org.apache.coyote.AbstractProtocol stop 信息: Stopping ProtocolHandler ["ajp-apr-21963"] 二月 06, 2016 3:24:46 下午 org.apache.coyote.AbstractProtocol destroy 信息: Destroying ProtocolHandler ["http-apr-8888"] 二月 06, 2016 3:24:46 下午 org.apache.coyote.AbstractProtocol destroy 信息: Destroying ProtocolHandler ["ajp-apr-21963"] Disconnected from server
分析过滤器的执行过程,先看过滤器生命周期:
首先访问了 http://localhost:8888/index.jsp页面,之后请求资源被拦截,进入过滤器,当处理逻辑完毕,返回响应资源时,类似数据结构里的栈,最开始的过滤器最后一个被调用,反过来通过过滤器,才把资源返回给页面。
使用debug进行源码分析,我发现它是有这样一个类调用的doFilter方法,它实现了FilterChain, CometFilterChain两个接口,其中FilterChain接口是真正的抽象策略接口
ApplicationFilterChain类是个final类,在这里可以把它直接当作抽象的处理策略类(过滤器接口),它做了一些事情,针对责任链模式的使用,它用一个 ApplicationFilterConfig 类的数组 filters 保存各个具体的过滤器对象
ApplicationFilterConfig 是一个Filter 的容器,它的主要作用是读取web.xml文件配置
而且该类的内部聚合了一个Filter接口的引用,该Filter就是之前测试类FilterDemo实现的那个接口
仔细观察发现,Filter接口的方法doFilter的参数有一个FilterChain接口类型的参数,如此把两个doFilter结合
这样测试类实现Filter接口,同时该接口的doFilter方法里又有一个责任链模式里真正抽象的处理策略接口 FilterChain 的参数,在方法内部调用接口FilterChain的doFilter方法
在实现类FilterDemo里使用
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("过滤器执行了!"); // 让下一个资源执行 filterChain.doFilter(servletRequest, servletResponse); }
方法内的doFilter就是真正的抽象策略类接口的抽象方法,而ApplicationFilterChain类又实现了FilterChain接口,ApplicationFilterChain类内部聚合了所有的过滤器Filter,如此就清晰了。继续debug,发现程序进入了这个类:StandardWrapperValve
其中有这样两句代码:
我发现里面有我想要的,开始说的责任链模式里抽象处理策略类!ApplicationFilterChain类,而这个类的引用被一个ApplicationFilterFactory(应该是使用了工厂模式)的createFilterChain方法实例化,进入ApplicationFilterFactory查看createFilterChain方法:
发现别有洞天!原来该工厂类的createFilterChain方法里实例化了之前的抽象处理策略类,继续;
调用了addFilter方法,回到抽象处理策略类,发现这是在初始化Filter数组!!!
void addFilter(ApplicationFilterConfig filterConfig) { ApplicationFilterConfig[] newFilters = this.filters; int len$ = newFilters.length; for(int i$ = 0; i$ < len$; ++i$) { ApplicationFilterConfig filter = newFilters[i$]; if(filter == filterConfig) { return; } } if(this.n == this.filters.length) { newFilters = new ApplicationFilterConfig[this.n + 10]; System.arraycopy(this.filters, 0, newFilters, 0, this.n); this.filters = newFilters; } this.filters[this.n++] = filterConfig; }
发现了貌似是JDK一个傻逼问题!?开头明明定义了常量10啊,为啥里面还写10这个魔鬼数字呢?且这个常量10没有被使用!我去!!!匪夷所思。高人可以指点!
StandardWrapperValue类在初始化过滤器保存的数组之后,就调用ApplicationFilterChain的doFilter方法
而ApplicationFilterChain的doFilter方法内部,又调用了自己的私有工具方法internalDoFilter方法
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if(Globals.IS_SECURITY_ENABLED) { final ServletRequest req = request; final ServletResponse res = response; try { AccessController.doPrivileged(new PrivilegedExceptionAction() { public Void run() throws ServletException, IOException { ApplicationFilterChain.this.internalDoFilter(req, res); return null; } }); } catch (PrivilegedActionException var7) { Exception e = var7.getException(); if(e instanceof ServletException) { throw (ServletException)e; } if(e instanceof IOException) { throw (IOException)e; } if(e instanceof RuntimeException) { throw (RuntimeException)e; } throw new ServletException(e.getMessage(), e); } } else { this.internalDoFilter(request, response); } }
显然internalDoFilter方法里的filter.doFilter(request, response, this);就是调用我们前面创建的测试类FilterDemo中的doFilter()方法。
而FilterDemo 中的doFilter()方法会继续调用 chain.doFilter(request, response); 方法,而这个 chain 其实就是 ApplicationFilterChain,所以调用过程又回到了上面调用 doFilter 和调用 internalDoFilter 方法,这样执行直到里面的过滤器全部执行。太多了,差不多可以了,大体就是:
简单的局部的类图如下:
这样一个情景,有一个restful接口,需要用户输入很多参数,自然在调用之前需要多参数进行过滤校验,之后在执行真正的业务逻辑处理,请结合AOP编写一个拦截器
什么是AOP,这里不表,且目前我给了两个实现模拟的例子,一个是用了代理模式+责任链,一个是利用list集合列表进行控制,看这篇总结的文章:
大量逻辑判断优化的思路——责任链模式复习总结及其和状态模式对比
原文:http://www.cnblogs.com/kubixuesheng/p/5182611.html