文章目录
Demo 地址: https://github.com/ooblee/HelloDesignPattern
1. 定义
责任链模式(Chain of Responsibility Pattern):避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
一个线性流程的处理。
现实世界的模型:
- 案件审判,地方法庭->县法院->市中级人民法院->省高级人民法院->最高人民法院。
- 经费报销,经理 ->主管->总监->财务。
上面的经费报销,报销申请沿着链往下走,如果有一个节点处理后就不再往下传递。
责任链模式会把每个节点封装成一个类,然后连接起来,传递请求直到被拦截处理掉。
2. 设计
责任链的核心在于抽象处理者的实现。
- 抽象处理者,定义处理请求的接口,持有下一个处理类的引用。
- 具体处理者,实现处理请求的接口,
具体处理者相互连接形成一个链表。请求沿着链表传递,直到被处理。
我这里举例两种设计。
2.1. 链表实现
整体的实现类似于链表。
抽象处理者,会携带下一个处理者的引用。
public abstract class AbstractHandler {
private AbstractHandler successor;
public abstract void handleRequest();
public void setSuccessor(AbstractHandler successor) {
this.successor = successor;
}
public AbstractHandler getSuccessor() {
return successor;
}
public void next() {
if (getSuccessor() != null) {
getSuccessor().handleRequest();
}
}
}
具体处理者,如果需要传递请求,调用 next
,否则不调用(效果等同于拦截请求)。
public class HandlerA extends AbstractHandler {
public void handleRequest() {
System.out.println("A 处理");
// 传递给下一个处理节点
next();
}
}
使用者组装链并发送请求:
public class TestChainOfResponsibility {
public static void main(String[] args) {
AbstractHandler handlerA = new HandlerA();
AbstractHandler handlerB = new HandlerB();
AbstractHandler handlerC = new HandlerC();
// 建链
handlerA.setSuccessor(handlerB);
handlerB.setSuccessor(handlerC);
handlerC.setSuccessor(null);
// 处理请求
handlerA.handleRequest();
}
}
2.2. 非链表实现
上面的设计有一个问题,就是作为责任链的节点,需要去持有下一个节点的引用。
是否可以把这层关系剥离出去,让节点更加独立?
这也是很多框架使用的设计,会增加一个 Chain 类来维持整个处理者列表,由 Chain 类来驱动任务的传递。
这里给出一个简单的模型。
抽象 Chain 类,定义驱动下一个处理者的方法:
public interface IHandlerChain {
void handle(Request request, Response response);
}
抽象处理者,处理方法会携带 Chain 类:
public interface IHandler {
void handleRequest(Request request, Response response, IHandlerChain chain);
}
具体 Chain 类,维持责任链表,实现驱动下一个处理者的方法:
public class HandlerChain implements IHandlerChain {
private final List<IHandler> handlerList;
private int currentPos;
public HandlerChain(List<IHandler> handlerList) {
this.handlerList = handlerList;
currentPos = 0;
}
public void handle(Request request, Response response) {
if (currentPos >= handlerList.size()) {
return;
}
IHandler handler = handlerList.get(currentPos++);
handler.handleRequest(request, response, this);
}
}
具体处理者,实现处理方法,无需关联到其他处理者,非常独立。使用 Chain 来驱动请求的传递。
public class HandlerA implements IHandler {
public void handleRequest(Request request, Response response, IHandlerChain chain) {
System.out.println("A 处理了请求");
// 传递下一个请求
chain.handle(request, response);
}
}
如果不传递的话不用调用 Chain 即可。
使用者组装链表,并发送请求获取响应:
public class TestChainOfResponsibility {
public static void main(String[] args) {
// 建链
IHandler handlerA = new HandlerA();
IHandler handlerB = new HandlerB();
IHandler handlerC = new HandlerC();
List<IHandler> handlerList = new ArrayList<IHandler>();
handlerList.add(handlerA);
handlerList.add(handlerB);
handlerList.add(handlerC);
IHandlerChain handlerChain = new HandlerChain(handlerList);
// 处理请求
Request request = new Request();
Response response = new Response();
handlerChain.handle(request, response);
}
}
抽象出 Chain,让各个处理者更加独立,无任何关联。
抽象出 Chain 类还有一个好处,如果处理者需要关联到外部环境,需要用到环境上下文的一些对象,可以通过 Chain 类的传输。可以参考 OkHttp 的 RealInterceptorChain 的实现。
2.3. 纯责任链和不纯责任链
纯的责任链有这样的要求:
- 处理节点必须承担全部责任或直接传递给下一个处理者。
- 请求必须被一个处理节点处理掉。
按照纯责任链的设计,每个处理节点不会承担部分责任,然后请求也不会没有被接收的现象。
但实际应用大部分是不纯的责任链。
3. 应用
责任链可以在下面的场景使用。
一个请求有多个处理对象,然后:
- 处理者可以动态配置。
- 处理者的顺序可以动态配置。
- 请求不需要指定具体的处理者。
实际的应用根据业务场景会增加一些比较复杂的设计,提高扩展性和灵活性。
- 比如会有封装好的请求和响应。响应通过处理方法返回,当前处理者可以获取到响应再进一步处理,比如 Servlet 的 Filter 链,OkHttp 的拦截器链。
- 比如下一个处理者的关联和处理顺序单独放在一个类进行维护,各个处理节点不需要持有下一个处理节点的引用。比如 Servlet 的 FilterChain,Filter 可以调用 FilterChain 去触发下一个节点。
责任链各个处理节点独立变化,可以根据业务场景很方便地进行增删改。符合开闭原则
例如这样的场景:
要求这样处理 A -> B -> C。
需求变化,B 的重要性提升,需要提前处理,那么就可以改下连接关系 B -> A -> C。
需求变化,有多了一个 D 请求,需要在 A 和 C 之间执行,则 B -> A -> D -> C。
…
请求发起者不需要关心到这些变化或者过程,只要关心请求的发送和响应的接收。
3.1. Servlet 的 Filter 链
Java Servlet 用来获取请求,生成响应数据并返回。绝大部分用来处理 HTTP 协议。
Filter 属于 Servlet API 的一部分,可以用来对 HttpServletRequest 进行预处理。也可以对 HttpServletResponse 进行发送前处理。
在 Servelt 执行前,会先调用 Filter 对请求进行过滤。Servlet 返回的响应也可以通过 Filter 进行处理。
比如:
- 对 HttpServletRequest 进行拦截
- 检查或者修改 HttpServletRequest 的头部信息或者数据
- 检查或者修改 HttpServletResponse 的头部信息或者数据
比如 MVC 框架 Structs2 就是采用 Filter 拦截 HttpServletRequest,根据 url 地址分发到对应的控制器进行处理。
请求 HttpServletRequest 在处理链中传递,各个处理节点完成自己的任务,比如:
- 过滤非法请求
- 加入日志
- 编解码
Filter 类似于 AOP 编程中的一个切点,所有的 Filter 处理形成一个处理链。
整个实现使用了非链表实现的责任链模式。
抽象 Filter Chain 类:
public interface FilterChain {
public void doFilter ( ServletRequest request, ServletResponse response ) throws IOException, ServletException;
}
抽象 Filter 类:
public interface Filter {
public void init(FilterConfig filterConfig) throws ServletException;
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
public void destroy();
}
具体 Filter 类,比如用来打个日志:
@WebFilter(filterName = "log", urlPatterns = {"/*"})
public class LogFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("处理请求前");
chain.doFilter(request, response);
System.out.println("处理请求后");
}
}
组装过滤器链和发送请求由支持 Servlet 的 Web 服务器提供。
比如 Tomcat。在 ApplicationDispatcher 中的 invoke
,调用 ApplicationFilterFactory#createFilterChain 完成了对 FilterChain 的创建。
// Get the FilterChain Here
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
然后调用,处理请求。
filterChain.doFilter(request, response);
3.2. OkHttp 的 Interceptor 链
相关文章:
OkHttp 是一款基于 Java 实现的 HTTP 客户端。在 Android 平台应用广泛(Android 官方已经移除 HttpClient 直接使用 OkHttp)。
OkHttp 的请求处理过程使用的是拦截器链实现的。
一个 HTTP 客户端,对于用户发起的 HTTP 请求,需要什么工作?
- 连接失败的重试。
- 重定向的处理。
- 修改 header 信息。
- 响应缓存的实现。
- 建立连接。比如 TCP 连接或者 SSL 连接。
- 往 TCP 连接中写入请求并读取响应
正好是责任链模式的应用场景:一个请求对应多个处理者。
OkHttp 设计了 Inteceptor,作为抽象处理者。处理方法为 intercept
。
设计了 Chain 用来维护责任链,驱动请求的传递,并且获取一些环境信息。驱动下一个处理者的方法为 proceed
。
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
...
Response proceed(Request request) throws IOException;
...
}
}
Chain 的默认实现类为 RealInterceptorChain。处理者列表为 interceptors
。
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
...
private final int index;
...
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
throws IOException {
...
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
...
return response;
}
}
具体的处理者,根据我们上面分析的处理任务有这些:
- RetryAndFollowUpInterceptor,请求连接失败的重试或重定向。
- BridgeInterceptor,用来修改请求和响应的 header 信息。
- CacheInterceptor,比如获取到的 Response 带有 Date,Expires,Last-Modified,Etag 等 header,表示该 Response 可以缓存一定的时间,下次请求就可以不需要发往服务端,直接拿缓存的。
- ConnectInterceptor,使用 Socket 创建 TCP 连接,如果是 HTTPS 还会进行 TLS 握手。
- CallServerInterceptor,往 Socket 写入请求并读取响应。
OkHttp 组装和调用链的地方在 RealCall 里。看代码:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(new RetryAndFollowUpInterceptor(client));
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
整个拦截器链的流程如下:
3.3. Android 事件分发机制
相关文章:
任何 GUI 编程,都会遇到用户输入事件的处理。
在界面复杂,嵌套 View 非常复杂的情况下,有多个层级的情况下,会涉及到一个问题,事件该由哪个 View 来处理?
这里可以抽象成一个事件请求,存在多个处理者,应该由哪个处理者来执行的问题。
也是责任链模式的应用范畴了。
这里举 Android 事件分发机制的例子(其他平台其实原理也类似)。
在 Android 事件分发前,整个 View 树已经建立(这里使用的是组合模式)。处理者就是整棵 View 树的各个 View 节点。
事件会沿着整个 View 树传递,直到被拦截和消费。
事件核心为三个:
- 按下,MotionEvent.ACTION_DOWN。
- 移动,MotionEvent.ACTION_MOVE。
- 起来,MotionEvent.ACTION_UP。
在用户在界面上手指按下的那一刻,会触发一个 ACTION_DOWN 事件,系统会取出当前的 Activity,调用 dispatchTouchEvent
方法开始整个事件分发流程。
而作为处理者的 View 调用 dispatchTouchEvent
来处理事件,决定是否拦截和消费事件,或者继续传递给子 View。
整个流程如下:
4. 特点
4.1. 优势
- 解耦:请求发起者和请求响应者进行解耦。链的创建由使用者(客户端)创建。
- 动态组合:可以运行时动态组合链,根据需求增加或者减少处理节点。
- 易修改:处理节点独立变化,只处理自己关心的工作,修改方便。
- 易扩展:如果有新的处理需求,创建新的处理节点,然后使用者重新建链。是符合开闭原则的。
4.2. 缺点
- 执行未处理:没有明确接收者,或者配置不当,会出现请求没有处理。
- 执行的路径长:一次任务的处理需要多个节点的传递,性能会降低,调试不方便。
- 执行的死循环:建链不当导致环型链表,死循环。