责任链模式

模式动机

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

避免将消息发送者sender和消息处理者receiver之间的耦合。将多个receiver链接在一起,如果发现自己可以处理则进行处理,否则,将该消息传递给下一个消息处理者。

维基百科对责任链模式的定义:

责任链模式在面向对象程式设计里是一种软件设计模式,它包含了一些命令对象和一系列的处理对象。每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。

总的来说就是,你给我一个请求,我能处理我就处理,并且不往下传递;否则,我就把这个请求往下传递,让其他的处理者来处理。Servlet中的 Filter 使用的就是这种,首先对请求进行预处理,然后才传递给Servlet来处理。

责任链模式的适用场景

在以下场景下可以考虑使用责任链模式:

  • 不止一个处理对象时,需要给一个请求增加一些预处理时。如 Filter
  • 你想要向一个对象发出请求,而不想显式地指定接收方。这样就需要把消息处理者串成一个链,从而对自己可以处理的消息进行处理。
  • 需要动态的指定消息处理者时。

具体事例

考虑下面这个场景,一个兽人王国有一个国王OrcKing,国王会下达各种命令(Request), 比如:防御城堡(DEFEND_CASTLE)、折磨犯人(TORTURE_PRISONER)、收税(COLLECT_TAX)。而这些命令分别由指挥官 (OrcCommander)、政府官员(OrcOfficer )以及士兵(OrcSoldier)来执行。国王在下达命令的时候并不需要关心这个命令究竟由谁来执行。在这种场景下,可以把消息处理者OrcCommanderOrcOfficerOrcSoldier 串成一个链,国王下达命令后,消息处理者会检查当前这个消息自己能否执行,能执行就自己执行,不然就把该消息扔给下一个任务执行者。具体的类图如下:
责任链模式的类图
一个请求包括请求的类型,以及请求的描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 一个请求
*/
public class Request {
private final RequestType requestType;
private final String requestDescription;
private boolean handled;
public Request(final RequestType requestType, final String requestDescription) {
this.requestType = Objects.requireNonNull(requestType);
this.requestDescription = Objects.requireNonNull(requestDescription);
}
public String getRequestDescription() {
return requestDescription;
}
@Override
public String toString() {
return getRequestDescription();
}
}

OrcKing 具有下达命令的权利,同时它还拥有一个请求的处理链chain

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
/**
*
* OrcKing makes requests that are handled by the chain.
*
*/
public class OrcKing {

/**
* 责任链
*/
RequestHandler chain;

public OrcKing() {
buildChain();
}

/**
* 构建责任链
/
private void buildChain() {
chain = new OrcCommander().addRequestHandler(new OrcOfficer()).addRequestHandler(new OrcSoldier());
}
/**
* 发出请求
*/
public void makeRequest(Request req) {
chain.handleRequest(req);
}
}

下面是消息处理器,包括一个 RequestHandler 的抽象类以及OrcCommanderOrcOfficerOrcSoldier 三个消息处理类:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/**
*
* RequestHandler
*
*/
public abstract class RequestHandler
{
private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class);

private RequestHandler next;

public RequestHandler()
{
}

public RequestHandler(RequestHandler next)
{
this.next = next;
}

/**
* 处理请求
*/
public void handleRequest(Request req)
{
if (next != null)
{
next.handleRequest(req);
}
}

/**
* 在责任链的尾部添加消息处理类
*/
public RequestHandler addRequestHandler(RequestHandler handler)
{
RequestHandler temp = this;
while(temp.next != null)
temp = temp.next;
temp.next = handler;
return this;
}

protected void printHandling(Request req)
{
LOGGER.info("{} handling request \"{}\"", this, req);
}

@Override
public abstract String toString();
}


/**
*
* OrcOfficer
*
*/
public class OrcOfficer extends RequestHandler {

public OrcOfficer()
{
}

public OrcOfficer(RequestHandler handler) {
super(handler);
}

/**
* 重写父类的消息处理方法
*/
@Override
public void handleRequest(Request req) {
// 如果是折磨烦人,让我来
if (req.getRequestType().equals(RequestType.TORTURE_PRISONER)) {
printHandling(req);
req.markHandled();
} else {
// 否则,让父类来处理,也就是把消息往下传递
super.handleRequest(req);
}
}

@Override
public String toString() {
return "Orc officer";
}

}

/**
*
* OrcSoldier
*
*/
public class OrcSoldier extends RequestHandler {

public OrcSoldier()
{
}

public OrcSoldier(RequestHandler handler) {
super(handler);
}

@Override
public void handleRequest(Request req) {
if (req.getRequestType().equals(RequestType.COLLECT_TAX)) {
printHandling(req);
req.markHandled();
} else {
super.handleRequest(req);
}
}

@Override
public String toString() {
return "Orc soldier";
}
}
/**
*
* OrcCommander
*
*/
public class OrcCommander extends RequestHandler {

public OrcCommander()
{
}

public OrcCommander(RequestHandler handler) {
super(handler);
}

@Override
public void handleRequest(Request req) {
if (req.getRequestType().equals(RequestType.DEFEND_CASTLE)) {
printHandling(req);
req.markHandled();
} else {
super.handleRequest(req);
}
}

@Override
public String toString() {
return "Orc commander";
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Program entry point
*
* @param args command line args
*/
public static void main(String[] args) {

OrcKing king = new OrcKing();
// 发出一个请求
king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner"));
king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle"));
king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax"));
}

运行结果:

1
2
3
13:10:43.946 [main] INFO com.iluwatar.chain.RequestHandler - Orc officer handling request "torture prisoner"
13:10:43.949 [main] INFO com.iluwatar.chain.RequestHandler - Orc commander handling request "defend castle"
13:10:43.950 [main] INFO com.iluwatar.chain.RequestHandler - Orc soldier handling request "collect tax"

在项目中的使用

在数据开发平台的项目中,由于需要执行HiveMySQLOracle三种任务,客户端发送请求,后台使用三种不同的处理器分别执行不同的SQL,符合责任链模式的使用场景。因此把代码重构了下。

首先是抽象出来一个RunJobHandler借口,该接口有一个runJob方法。然后,设计一个AbstractRunJobHandler 对该接口进行基本的实现。然后,分别实现HQLJobHandler
MySQLJobHandlerOracleJobHandler继承自AbstractRunJobHandler
类之间的关系图如下:
数据开发平台责任链模式
具体的实现如下:

1
2
3
4
5
6
7
8
/**
* RunJobHandler接口
*/
public interface RunJobHandler
{
void runjob(WebSocketSession session,
RunJobRequestVO rjObject, ScriptDao scriptDao) throws Exception;
}

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
36
37
38
39
40
41
42
43
44
45
46
public abstract class AbstractRunJobHandler implements RunJobHandler
{
protected final Logger logger = LoggerFactory.getLogger(this.getClass());

AbstractRunJobHandler next;

/**
* 添加任务处理器
* @param jobHandler 任务处理器
* @return
*/
public AbstractRunJobHandler addRunJobHandler(AbstractRunJobHandler jobHandler)
{
AbstractRunJobHandler temp = this;
while (temp.next != null)
temp = temp.next;
temp.next = jobHandler;
return this;
}

@Override
public void runjob(WebSocketSession session,
RunJobRequestVO rjObject, ScriptDao scriptDao)
{
//默认实现,由next来执行
if (next != null)
next.runjob(session, rjObject, scriptDao);
}

protected List createLogList(String log)
{
// ommited...
}

/**
* 将错误发送到前端并存到数据库
* @param session
* @param scriptDao
* @param e
*/
protected void handleException(WebSocketSession session, ScriptDao scriptDao, Throwable e)
{
// ommited...
}

}

执行hive 任务的处理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class HQLJobHandler extends AbstractRunJobHandler
{

@Override
public void runjob(WebSocketSession session,
RunJobRequestVO rjObject, ScriptDao scriptDao)
{
if (BaseConstant.HIVE.equals(rjObject.getJobType()))
{
// 处理逻辑
}
else
{
super.runjob(session, rjObject, scriptDao);
}
}
}

MySQLJobHandler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MySQLJobHandler extends AbstractRunJobHandler
{

@Override
public void runjob(WebSocketSession session,
RunJobRequestVO rjObject, ScriptDao scriptDao)
{
if (BaseConstant.MYSQL.equals(rjObject.getJobType()))
{
// mysql的处理逻辑
}
else
{
super.runjob(session, rjObject, scriptDao);
}
}
}

OracleJobHandler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @author bjyangrubing
* @createTime 2017/8/29 15:15
* Description:
*/
public class OracleJobHandler extends AbstractRunJobHandler
{
@Override
public void runjob(WebSocketSession session,
RunJobRequestVO rjObject, ScriptDao scriptDao)
{
if (BaseConstant.ORACLE.equals(rjObject.getJobType()))
{
//oracle 的处理逻辑
}
else
{
super.runjob(session, rjObject, scriptDao);
}
}
}

客户端在使用的时候,首先build一个责任链:

1
2
3
4
5
6
7
8
9
   /**
* 获取到责任链
*/
private AbstractRunJobHandler getHandlerChain()
{
return new HQLJobHandler().addRunJobHandler(new OracleJobHandler()).addRunJobHandler(new MySQLJobHandler());
}
//直接运行了不用管具体怎么实现,这样就实现了方法调用者与处理者的松耦合。
jobHandler.runjob(session,rjObject, scriptDao);

其实这种情况下也可以用策略模式,本质上运行任务也是采用的不同的策略来实现的。