Chain of Responsibility Pattern
Title | Module | Category | Tags |
---|
Chain of Responsibility
|
chain-of-responsibility-design
|
Behavioral
|
|
背景
当面临将请求发送者与多个请求处理者之间进行解耦处理时, 责任链可以很好地应对这种情况, 即将所有请求的处理者通过前一对象标记其下一个对象的引用而构建成一条链, 当有请求发出时, 可将请求沿着这条链传递, 直到有对象处理它为止.
责任链模式简介
责任链模式主要是用于解耦请求和处理逻辑, 客户端只需要将请求发送到链路上即可, 而无需关心请求的处理细节及内容, 请求会自动进行传递直至有节点对象进行处理. 可以将各个处理节点看作一个个调度程序, 向各个节点发送指令, 形成一颗责任树, 某些情况下会出现递归调用情形.
结构图
责任链模式中主要包含两种角色:
- 抽象处理者:
Handler 抽象请求处理的方法, 并维护一个下一个处理节点 Handler 对象的引用 - 具体处理者:
ConcreteHandler 对请求进行处理, 若不感兴趣, 则进行转发
责任链模式的本质是解耦请求与处理, 让请求在处理链路中能进行传递与处理, 其核心在于将处理节点组合成链式结构, 并允许节点自身决定是否进行请求处理或转发, 请求流动起来, 类似一种流式处理.
应用场景
- 多个对象可以处理同一请求, 但具体该由哪个对象处理则在运行时动态决定
- 在不明确指定接收者请求的场景下, 向多个对象中的某个提交一个请求
- 可动态指定一组对象处理请求, 如权限校验框架的校验逻辑, 将各个维度的权限处理解耦之后再串联起来, 各自只需负责相关的职责.
代码示例
可以根据上述绘制的 UML 设计图, 先建立抽象处理器 Handler
package com.kyle.design.chain.general;
public abstract class Handler {
protected Handler nextHandler;
public void setNextHandler(Handler successor) {
this.nextHandler = successor;
}
public abstract void handleRequest(String request);
}
然后通过继承 Handler , 编写具体的处理者 ConcreteHandlerA 和 ConcreteHandlerB
package com.kyle.design.chain.general;
public class ConcreteHandlerA extends Handler {
public void handleRequest(String request) {
if ("requestA".equals(request)) {
System.out.println(this.getClass().getSimpleName()
+ "deal with request: " + request);
return;
}
if (this.nextHandler != null) {
this.nextHandler.handleRequest(request);
}
}
}
package com.kyle.design.chain.general;
public class ConcreteHandlerB extends Handler {
public void handleRequest(String request) {
if ("requestB".equals(request)) {
System.out.println(this.getClass().getSimpleName()
+ "deal with request: " + request);
return;
}
if (this.nextHandler != null) {
this.nextHandler.handleRequest(request);
}
}
}
通过建立客户端测试类, 模拟发出请求
package com.kyle.design.chain.general;
public class SendRequestDrive {
public static void main(String[] args) {
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
handlerA.setNextHandler(handlerB);
handlerA.handleRequest("requestB");
}
}
HandlerA 通过传递请求至 HandlerB 进行处理, 以下是测试结果
开源应用
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();
}
J2EE 标准中的 Filter 接口类, 相当于责任链模式中的 Handler 抽象角色, 那么是如何实现责任链的构成的呢? 通过另一个类, doFilter() 方法的最后一个参数可以看出另一个类的类型就是 FilterChain 类
public interface FilterChain {
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException;
}
FilterChain 类中只定义了一个 doFilter() 方法, J2EE 为我们提供了一种规范, 具体处理逻辑需要 使用者自己实现, 如 Spring 中对代理的责任链的设计
- Netty 中的串行化 Pipeline 的处理也采用了责任链模式
底层采用双向链表的数据结构, 将链路上的各个处理器串联起来, 当客户端的请求到来时, Netty 认为 Pipeline 中所有的处理器都有机会处理它. 故而入栈的请求全部从头节点往后开始传播, 一直传播到尾节点才会把消息释放掉, Netty 中有一个责任处理接口ChannelHandler
public interface ChannelHandler {
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
@Deprecated
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Sharable {
}
}
Netty 对责任处理接口功能做了更加细粒度的划分, 处理器主要被分为两种, 一种是入栈处理器 ChannelInboundHandler , 另一种是出栈处理器 ChannelOutboundHandler , 这两个接口均是继承自 ChannelHandler . 不过最终处理器节点都添加在 Pipeline 上, 对于处理器节点的增删的职责也规定在 ChannelPipeline 中
public interface ChannelPipeline
extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable<Entry<String, ChannelHandler>> {
ChannelPipeline addFirst(String name, ChannelHandler handler);
ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler);
ChannelPipeline addLast(String name, ChannelHandler handler);
ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler);
ChannelPipeline addAfter(String baseName, String name, ChannelHandler handler);
ChannelPipeline addFirst(ChannelHandler... handlers);
ChannelPipeline addLast(ChannelHandler... handlers);
ChannelHandler removeFirst();
ChannelHandler removeLast();
}
在 Netty 默认的实现类中是将所有的 Handler 串成一个链表
public class DefaultChannelPipeline implements ChannelPipeline {
static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelPipeline.class);
private static final String HEAD_NAME = generateName0(HeadContext.class);
private static final String TAIL_NAME = generateName0(TailContext.class);
private static final AtomicReferenceFieldUpdater<DefaultChannelPipeline, MessageSizeEstimator.Handle> ESTIMATOR =
AtomicReferenceFieldUpdater.newUpdater(
DefaultChannelPipeline.class, MessageSizeEstimator.Handle.class, "estimatorHandle");
final AbstractChannelHandlerContext head;
final AbstractChannelHandlerContext tail;
private final Channel channel;
private final ChannelFuture succeededFuture;
private final VoidChannelPromise voidPromise;
private final boolean touch = ResourceLeakDetector.isEnabled();
private Map<EventExecutorGroup, EventExecutor> childExecutors;
private volatile MessageSizeEstimator.Handle estimatorHandle;
private boolean firstRegistration = true;
private PendingHandlerCallback pendingHandlerCallbackHead;
private boolean registered;
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
}
对于 Pipeline 中的任意一个节点, 只要不是人为手动的往下传播下去, 这个事件将会将传播终止在当前节点. 对于入栈数据, 默认会传递到尾节点进行回收. 若不进行下一步传播, 事件将会终止在当前节点, 而对于出栈数据把数据写回客户端也意味着事件的终止.
- 安全框架 Spring Security 中权限校验责任链的设计
思考总结
? 责任链模式关键在于处理节点职责的划分和处理节点在责任链路中顺序的规划. 与装饰器模式相比较而言, 存在的不同之处在于: 对于装饰器, 所有的类都能够处理请求, 而对于责任链, 链路中恰好有一个类处理请求.
? 同时, 责任链模式可以与建造者模式结合使用, 由于其天然的链式结构特性, 通过建造者模式可以对处理节点对象进行自动地链式组装, 避免仅仅使用责任链模式引发的链式结构组装繁杂, 服务职责不单一的问题, 这样一来客户只需要指定处理节点对象, 而且引入建造者模式, 链式结构的构造(指定处理节点的顺序)可实现自主化的定义.
优点
- 将实际请求与接受方处理逻辑解耦, 便于后期扩展新的请求处理类(处理节点)
- 处理链路逻辑结构灵活, 可以通过改变链路结构动态新增或删减责任处理逻辑
- 请求处理者(节点对象)仅需关注自身感兴趣的请求进行处理, 不感兴趣对象直接转发给下一级节点对象
- 具备链式传递处理请求功能, 请求发送者无需知晓链路结构, 只需等待请求处理结果
缺点
- 责任链过长或是处理逻辑过长, 可能会影响系统处理的整体的性能
- 若是节点对象存在循环引用的时候, 会造成死循环, 导致系统可能崩溃
Reference
-
E.Gamma, R.Helm, R.Johnson, and Vlissides. Design Patterns Elements of Reusable Object Oriented Software. Addison-Wesley, 1995 -
Chain of Responsibility on Wiki
?
|