设计模式之责任链模式

责任链模式

责任链模式

责任链模式(Chain of Responsibility Pattern)是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。当一个请求从链式的首端发出时,会沿着链的路径依次传递给每一个书点对象,直至有对象处理这个请求为止,属于行为型模式。

通用UML类图

image-20210424183802202

从UML类图中,可以看到,责任链模式主要包含两种角色:

抽象处理者(Handler):定义一个请求处理的方法,并维护一个下一个处理节点Handler对象的引用;

具体处理者(ConcreteHandler):对请求进行处理,如果不感兴趣,则进行转发。

责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;

理解责任链模式应当理解的是其模式(道)而不是其具体实现(术),责任链模式的独到之处是其将节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动了起来。

责任链模式的应用场景

在日常生活中责任链模式还是比较常见的,我们平时工作处理一些事务,往往是各部门协同合作的完成某一个任务。而每个部门都有各自的职责,因此,很多时候事情完成一半,便会转交给下一个部门直到所有部门都过一遍之后事情才能完成。还有我们平时俗话说的过五关、斩六将其实也是一种责任链。

责任链模式主要是解耦了请求与处理,客户只需将请求发送到链上即可,无需关心请求的具体内容和处理细节,请求会自动进行传递直至有节点对象进行处理。适用于以下应用场景

1、多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定;

2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求;

3、可动态指定一组对象处理请求。

通用代码

抽象处理者(Handler)

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Handler
{

protected Handler nextHandler;

public void setNextHandler(Handler nextHandler)
{
this.nextHandler = nextHandler;
}

abstract void handlerRequest(String req);
}

具体处理者(ConcreteHandler)

ConcreteHandlerA

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ConcreteHandlerA extends Handler
{
@Override
void handlerRequest(String req)
{
System.out.println("ConcreteHandlerA handlerRequest " + req);
// 可做一些处理,决定是否往下传递
if (nextHandler != null)
{
nextHandler.handlerRequest(req);
}
}
}

ConcreteHandlerB

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ConcreteHandlerB extends Handler
{
@Override
void handlerRequest(String req)
{
System.out.println("ConcreteHandlerB handlerRequest " + req);
// 可做一些处理,决定是否往下传递
if (nextHandler != null)
{
nextHandler.handlerRequest(req);
}
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
public class Main
{
public static void main(String[] args)
{
ConcreteHandlerA concreteHandlerA = new ConcreteHandlerA();
ConcreteHandlerB concreteHandlerB = new ConcreteHandlerB();
concreteHandlerA.setNextHandler(concreteHandlerB);
concreteHandlerA.handlerRequest("req");
}
}

输出

1
2
ConcreteHandlerA handlerRequest req
ConcreteHandlerB handlerRequest req

责任链模式和建造者模式结合使用

因为责任链模式具备链式结构,在上述代码中负责组装链式结构的角色是ConcreteHandler,当链式结构较长时,ConcreteHandler的工作会非常繁琐,并且ConcreteHandler代码相对臃肿,且后续更改处理者或消息类型时,都必须在ConcreteHandler中进行修改,不符合开闭原则。产生这些问题的原因就是因为链式结构的组装过于复杂,而对于复杂结构的创建,很自然我们就会想到建造者模式,使用建造者模式,我们完全可以对ConcreteHandler指定的处理节点对象进行自动链式组装,客户只需指定处理节点对象,其他任何事情都无需关心,并且客户指定的处理节点对象顺序不同,构造出来的链式结构也随之不同。我们来改造一下,修改Handler的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public abstract class Handler
{

protected Handler nextHandler;

public void next(Handler nextHandler)
{
this.nextHandler = nextHandler;
}

abstract void handlerRequest(String req);

public static class Builder
{
private Handler head;
private Handler tail;

public Builder addHandler(Handler handler)
{
if (this.head == null)
{
this.head = this.tail = handler;
return this;
}
this.tail.next(handler);
this.tail = handler;
return this;
}

public Handler build()
{
return this.head;
}
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main
{
public static void main(String[] args)
{

ConcreteHandlerA concreteHandlerA = new ConcreteHandlerA();
ConcreteHandlerB concreteHandlerB = new ConcreteHandlerB();
Handler.Builder builder = new Handler.Builder();
builder.addHandler(concreteHandlerA)
.addHandler(concreteHandlerB);

builder.build().handlerRequest("req");
}
}

输出

1
2
ConcreteHandlerA handlerRequest req
ConcreteHandlerB handlerRequest req

责任链模式在源码中的体现

首先我们来看责任链模式在JDK中的应用,来看一个J2EE标准中非常常见的Filter类:

1
2
3
4
5
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接口的定义非常简单,相当于责任链模型中的Handler抽象角色,那么它是如何形成一条责任链的呢?看另外一个类,其实在doFilter() 方法的最后一个参数我们已经看到其类型是FilterChain类,来看它的源码:

1
2
3
public interface FilterChain {
public void dofilter(ServletRequest request, ServletResponse response) throws IoException, ServletException
}

FilterChain类中也只定义了一个doFilter()方法,通过调用它串联成一个责任链,实际上J2EE只是定义了一个规范,具体处理逻辑是由使用者自己来实现。

Netty中非常经典的串行化处理Pipeline就采用了责任链设计模式。它底层采用双向链表的数据结构,将链上的各个处理器串联起来。客户端每一个请求的到来,Netty都认为Pipeline中的所有的处理器都有机会处理它。因此,对于入栈的请求全部从头节点开始往后传播,一直传播到尾节点才会把消息释放掉。

Netty的责任处理器接口ChannelHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface ChannelHandler {

...
//当handler被添加到真实的上下文中,并且准备处理事件时被调用 handler被添加进去的回调
/**
* Gets called after the {@link ChannelHandler} was added to the actual context and it's ready to handle events.
*/
void handlerAdded(ChannelHandlerContext ctx) throws Exception;

// handler被移出的后的回调
/**
* Gets called after the {@link ChannelHandler} was removed from the actual context and it doesn't handle events
* anymore.
*/
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
...
}

Netty对责任处理接口做了更细粒度的划分,处理器被分成了两种,一种是入栈处理器ChannellnboundHandler,另一种是出栈处理器ChannelOutboundHandler,这两个接口都继承自ChannelHandler。而所有的处理器最终都在添加在Pipeline上。所以,添加删除责任处理器的接口的行为在Netty的ChannelPipeline中的进行了规定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 addLast(EventExecutorGroup group, String name, ChannelHandler handler);

ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler);

ChannelPipeline addBefore(EventExecutorGroup group, String baseName, String name, ChannelHandler handler);

ChannelPipeline addAfter(String baseName, String name, ChannelHandler handler);

ChannelPipeline addAfter(EventExecutorGroup group, String baseName, String name, ChannelHandler handler);

ChannelPipeline addFirst(ChannelHandler... handlers);

ChannelPipeline addFirst(EventExecutorGroup group, ChannelHandler... handlers);

ChannelPipeline addLast(ChannelHandler... handlers);

ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler... handlers);

ChannelPipeline remove(ChannelHandler handler);

ChannelHandler remove(String name);
...
}

在默认的实现类中将所有的Handler都串成了一个链表。

1
2
3
4
5
6
7
public class DefaultchannelPipeline implements ChannelPipeline {
static final Internallogger logger = InternalloggerFactory.getInstance(DefaultchannelPipeline.class);
...
final AbstractchannelHandlerContext head;
final AbstractchannelHandlerContext tail;
...
}

在Pipeline中的任意一个节点,只要我们不手动的往下传播下去,这个事件就会终止传播在当前节点。对于入栈数据,默认会传递到尾节点进行回收。如果我们不进行下一步传播,事件就会终止在当前节点。对于出栈数据把数据写会客户端也意味着事件的终止。

责任链模式的优缺点

优点:

1、将请求与处理解耦;

2、请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转发给下一级节点对象;

3、具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果;

4、链路结构灵活,可以通过改变链路结构动态地新增或删减责任;

5、易于扩展新的请求处理类(节点),符合开闭原则。

缺点:

1、责任链太长或者处理时间过长,会影响整体性能;

2、如果节点对象存在循环引用时,会造成死循环,导致系统崩溃;

打赏

请我喝杯咖啡吧~

支付宝
微信