责任链模式CoR (Chain of Responsibility)
概述
责任链模式是一种设计模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
责任链可以是一条直线、一个环或者一个树形结构,客户端实际上无须关心请求的处理细节以及请求的传递,只需将请求发送到链上即可,从而实现请求发送者和请求处理者解耦。
对责任链的理解,关键在于对链的理解,即包含如下两点:
1.链是一系列节点的集合,在责任链中,节点实质上是指请求的处理者;
2.链的各节点可灵活拆分再重组,在责任链中,实质上就是请求发送者与请求处理者的解耦。
在什么情况下使用责任链模式
在下面的情况下使用责任链模式:
第一、系统已经有一个由处理者对象组成的链。这个链可能由复合模式给出,
第一、当有多于一个的处理者对象会处理一个请求,而且在事先并不知道到底由哪一个处理者对象处理一个请求。这个处理者对象是动态确定的。
第二、当系统想发出一个请求给多个处理者对象中的某一个,但是不明显指定是哪一个处理者对象会处理此请求。
第三、当处理一个请求的处理者对象集合需要动态地指定时。
主要优点
职责链模式的主要优点如下:
(1) 职责链模式使得一个对象无须知道是其他哪一个对象处理其请求,对象仅需知道该请求会被处理即可,接收者和发送者都没有对方的明确信息,且链中的对象不需要知道链的结构,由客户端负责链的创建,降低了系统的耦合度。
(2) 请求处理对象仅需维持一个指向其后继者的引用,而不需要维持它对所有的候选处理者的引用,可简化对象的相互连接。
(3) 在给对象分派职责时,职责链可以给我们更多的灵活性,可以通过在运行时对该链进行动态的增加或修改来增加或改变处理一个请求的职责。哪一个对象最终处理一个命令可以因为由那些对象参加责任链、以及这些对象在责任链上的位置不同而有所不同。
(4) 在系统中增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可,从这一点来看是符合“开闭原则”的。
主要缺点
职责链模式的主要缺点如下:
(1) 由于一个请求没有明确的接收者,那么就不能保证它一定会被处理,该请求可能一直到链的末端都得不到处理;一个请求也可能因职责链没有被正确配置而得不到处理。
(2) 对于比较长的职责链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响,而且在进行代码调试时不太方便。
(3) 如果建链不当,可能会造成循环调用,将导致系统陷入死循环。
纯的与不纯的责任链模式
一个纯的责任链模式要求一个具体的处理者对象只能在两个行为中选择一个:一是承担责任,而是把责任推给下家。不允许出现某一个具体处理者对象在承担了一部分责任后又 把责任向下传的情况。
在一个纯的责任链模式里面,一个请求必须被某一个处理者对象所接收;在一个不纯的责任链模式里面,一个请求可以最终不被任何接收端对象所接收。
纯的责任链模式的实际例子很难找到,一般看到的例子均是不纯的责任链模式的实现。
实现
职责链模式需要一个总接口,用来定义处理对象的公共部分(一般使用抽象类来定义),公共部分包括:一个后继处理器,设置和获取后继处理器的方法,具体的请求处理方法(这个方法需要在每个具体处理对象中实现),这里定义为抽象方法。
已有实现的例子
在实际软件开发中,如果遇到有多个对象可以处理同一请求时可以考虑使用职责链模式,最常见的例子包括在 Java Web 应用开发中创建一个过滤器(Filter)链来对请求数据进行过滤(中文字符乱码的处理)、在工作流系统中实现公文的分级审批、在Struts应用中添加不同的拦截器(常用的有类型转化、异常处理,数据校验…)以增强Struts2的功能等,还有下面dom事件处理也是责任链模式。
DHTML中的事件处理
浏览器的DOM(Document Object Model)模型中的事件处理均采用责任链模式。
Netscape的事件模型
Netscape的事件处理机制叫做“事件捕捉”(Event Capturing)。在事件捕捉机制里面,一个事件是从DOM的最高一层向下传播,也就是说,window对象是第一个接到事件的,然后是document对象,如此往下---事件的产生对象反而是最后一个接到事件的。
如果要是一个对象捕获某一个事件,只需要调用captureEvent()方法;如果要使一个对象把某一个事件向下传而不处理此事件,只需要对此对象使用releaseEvents方法即可。下面考察一个简单的事件捕获和传递的例子。
Internet Explorer的事件模型
Internet Explorer处理事件的方式与Netscape既相似又不同。当一个事件发生在Internet Explorer所浏览的网页中时,Internet Explorer会使用DHTML的“Event Bubbling”即事件浮升机制处理此事件。Internet Explorer的DOM模型是html对象等级结构和事件处理机制。在DOM里面,每一个html标示都是一个DOM对象,而每一个DOM对象都可以产生事先定义好的几个事件中的一个(或几个)。这样的一个事件会首先发生在事件所属的对象上,然后向上传播,传到此对象所属的容器对象上,如此等等。因此,事件浮升机制恰恰是事件捕捉机制的相反面。
在Event Bubbling机制里面,产生事件的对象首先会收到事件。然后,事件会依照对象的等级结构向上传播。比如一个DIV里有一个Form,Form里面又有一个Button,那么当Button的onclick事件产生时,Form的onclick事件代码就会被执行。然后,事件就会传到DIV对象。如果DIV对象的onclick事件有任何代码的话,这代码就会被执行,然后事件继续沿着DOM结构上行。
如果要阻止事件继续向上传播,可以在事件链的任何一个节点上把cancelBubble性质设置成True即可。
Internet Explorer 浏览器几乎为所有的 HTML 标识符都提供了事件句柄,因此Internet Explorer不需要captureEvents()方法和releaseEvents()方法来捕获和释放事件。下面的JavaScript语句指定了document对象的onclick事件的处理方法。
AOP理念与CoR模式
用传统的面向对象方法实现责任链模式虽然能够满足责任链模式要求的一切特征,在应用上也有很多实例,但是仍然存在者一些明显的缺陷和不足。比如,各个请求处理者除了实现自身应当处理的逻辑外还要实现责任链的结构(即successor属性及其Setter),也就是说,责任链的建立和指派包含在实现角色的类中,并没有抽象出来,这直接导致责任链的指派不够灵活。
AOP 思想的精髓能够将横向的关注点分离出来,这大大提高了我们认识世界和抽象世界的能力。实际上,责任链模式的缺陷主要在于具体实现角色的对象中存在着共同的行为——实现责任链结构的行为,而这些行为并没有被抽象出来,而用 AOP 改进责任链模式的关键就是要将责任链结构的实现用切面抽象出来,使得各个对象只关注自身必须实现的功能性需求。实际上,用AOP思想实现责任链模式时仍然保留了 Client,Handler 和 ConcreteHandler 三个角色,不同点是增加了实现责任链的切面,即 HandlerChain,利用AOP理念来改进责任链模式可以准确地分离出责任链模式中不同角色的共同行为。
java web filter实现
当客户端发出Web资源的请求时,Web服务器根据应用程序配置文件设置的过滤规则进行检查,若客户请求满足过滤规则,则对客户请求/响应进行过滤(拦截),期间我们可以对请求信息 (请求头和请求数据)进行检查或改动,然后对请求放行以便由过滤链中的其他过滤器进行处理,最后把请求/响应交给请求的Web资源(Servlet)处理。同样地,在这个过程中我们可以修改响应信息,从而完成一定的任务。
过滤链的好处是,发出请求的客户端并不知道链上的哪一个过滤器将处理这个请求,这使得系统可以在不影响客户端的情况下 动态地重新组织链和分配责任,并且在执行过程中的任何时候都可以打断,只要不执行chain.doFilter()就不会再执行后面的过滤器和请求的内容,这显然可以看作是 非纯责任链模式 的一种典型实现。
实质上,Filter 的实现既体现了AOP的理念,也体现了责任链模式的精髓。AOP的主要的意图是将日志记录、性能统计、安全控制、事务处理、异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非主导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。以处理中文字符乱码问题为例,它并非是业务逻辑的内容却又分布在各个请求处理器中,所以对于这些内容的处理,我们就可以基于AOP的思想将其提取出来(AOP中的切面),使用Filter进行整体设置。这种方式相当于对类中的内容做进一步的抽象,使我们的系统更加灵活,更加能应对变化,也进一步提高了代码复用。
此外,Filter 的实现体现了责任链模式的精髓,即将请求的发送者与请求的处理者解耦,从而使得系统更灵活,也更容易扩展。就像Servlet规范对Filter描述的那样,过滤链是由Servlet容器提供给开发者的一种过滤器调用的视图,过滤器使用过滤链去调用链中的下一个过滤器去处理请求,特别地,如果当前过滤器时过滤链中的最后一个过滤器,过滤链将把它交给相应的资源处理器(Servlet)进行处理。更进一步地说,使用过滤链对请求进行过滤的好处就是,发出请求的客户端并不知道链上的哪一个过滤器将处理这个请求,这使得系统可以在不影响客户端的情况下,动态地重新组织链和分配责任。并且,在执行过程中的任何时候都可以直接返回结果,也就是说,只要不执行 chain.doFilter() 就不会对请求放行,也就不会再执行后面的过滤器和请求的内容。这显然可以看作是 非纯责任链模式 的一种典型实现。
显然,FilterChain 本身就是对责任链切面的抽象,是对传统责任链模式的一个改进,整个 Filter 机制本身也是AOP思想与责任链模式的融合的最佳实践。
例子:对字符串过滤。
一个抽象 Filter,三个具体的 Filter,包括 HTMLFilter,SensitiveFilter 和 FaceFilter; FilterChain 用于对处理链(责任链切面)的抽象。此外,Request 和 Response 用于对请求消息和响应消息的抽象,Client 用于对客户端的抽象,其类图如下所示:
1.抽象处理者:Filter
publicinterfaceFilter{
//每个Filter均为FilterChain的成员, Filter持有FilterChain的引用,以便调用链条中的各处理者
void doFilter(Request request, Response response, FilterChain chain);
}
2、具体处理者:HTMLFilter,SensitiveFilter 和 FaceFilter
// 将请求消息中的"<>"替换成"[]"
public classHTMLFilterimplementsFilter{
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
// process HTML Tag
String msg = request.getRequest().replace("<", "[").replace(">", "]");
request.setRequest(msg);
chain.doFilter(request, response);
response.setResponse(response.getResponse() + "--->HTMLFilter");
}
}
//将请求消息中的"被就业"替换成"就业"
class SensitiveFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
String msg = request.getRequest().replace("被就业", "就业");
request.setRequest(msg);
chain.doFilter(request, response);
response.setResponse(response.getResponse() + "--->SensitiveFilter");
}
}
// 将请求消息中的":)"替换成"笑脸"
class FaceFilter implements Filter {
public void doFilter(Request request, Response response, FilterChain chain) {
String msg = request.getRequest().replace(":)", "笑脸");
request.setRequest(msg);
chain.doFilter(request, response);
response.setResponse(response.getResponse() + "--->FaceFilter");
}
}
3、过滤链的抽象:FilterChain
// 对过滤链的抽象(横切关注点),是多个过滤器的聚集,本质上,FilterChain 也可以看作是一个大的Filter
public classFilterChain{
List filters = new ArrayList();
int index = 0;
// 链式编程
public FilterChain addFilter(Filter filter){
filters.add(filter);
return this; // 返回自身
}
public void doFilter(Request request, Response response) {
if(index == filters.size()) return;
Filter filter = filters.get(index);
index++;
filter.doFilter(request, response, this);
}
}
4、请求和响应的抽象:Request 和 Response
// 对请求消息的抽象
public class Request{
// 请求消息 private String request;
public String getRequest() {
return request;
}
public void setRequest(String request) {
this.request = request;
}
}
// 对响应消息的抽象
class Response {
// 响应消息
private String response;
public String getResponse() {
return response;
}
public void setResponse(String response) {
this.response = response;
}
}
5、客户端的抽象:Client
public classClient{
public static void main(String[] args) {
// 待处理消息
String msg = "大家好 :),,敏感,被就业,网络授课没感觉...";
// 设置请求消息
Request request = new Request();
request.setRequest(msg);
// 设置响应消息
Response response = new Response();
response.setResponse("Response");
// 设置处理链
FilterChain chain = new FilterChain();
chain.addFilter(new HTMLFilter()).addFilter(new SensitiveFilter())
.addFilter(new FaceFilter());
// 开始处理
chain.doFilter(request, response);
// 消息的预处理结果
System.out.println(request.getRequest());
// 消息的后处理结果
System.out.println(response.getResponse());
}
}
/* Output(完全一致):
大家好 笑脸,[script],敏感,就业,网络授课没感觉...
Response--->FaceFilter--->SensitiveFilter--->HTMLFilter
*///:~
实际上,本示例基本模拟了Java Web 中过滤器的工作流程,也反映了AOP思想和责任链模式的精髓。 对于一个给定的请求消息,我们可以从下图中的方法调用栈中看出,将依次由 HTMLFilter,SensitiveFilter 和 FaceFilter 三者进行预处理,最后再依次由 FaceFilter,SensitiveFilter 和 HTMLFilter 处理(这个可以从输出中看出)。
实际上,FilterChain 本身也可以看作是一个大的Filter,更进步地说,FilterChain 本身也可以实现 Filter 接口,这样做的优点是,我们不但可以在客户端可以任意添加具体的过滤器,还可以添加过滤链;但带来的缺点是将FilterChain 和 Filter 耦合在了一起,也就是说,FilterChain与Filter的doFilter方法必须一样,而实际上FilterChain的doFilter方法并不需要FilterChain参数。花开生两面,有利就有弊,读者可以尝试让FilterChain 本身实现 Filter 接口,体会一下这个思想。