设计模式学习(责任链模式)

什么是责任链模式

责任链模式的主要思想是将请求的发送者和接收者解耦,通过一条责任链来处理请求。在这个模式中,多个对象都有机会处理请求,直到其中一个对象处理了该请求或者没有对象可以处理该请求为止。

具体来说,责任链模式包括一组处理对象(处理器),每个处理对象都包含一个指向下一个处理对象的引用。当有请求发生时,请求首先被发送到第一个处理对象,如果该对象能够处理该请求,则处理请求并返回响应;如果该对象无法处理该请求,则将请求传递给下一个处理对象,直到有对象能够处理该请求或者所有对象都不能处理为止。

这种方式可以有效地避免请求发送者与接收者之间的紧耦合关系,同时也可以动态地改变处理对象的顺序或增加新的处理对象,从而提高系统的灵活性和可扩展性。

责任链模式在开源项目中应用场景还是比较常见的,比如Tomcat中的Filter处理链、Netty中的ChannelHandler处理链、Dubbo RPC中的consumer侧的Filter链等等。责任链模式应用在业务流程中的 多个同类型操作场景,相当于对一个复杂较长的操作进行分段处理,这样对扩展性友好,新增操作阶段时更加灵活。这种可以理解为分片思想,降低业务流程操作的复杂度。

简单的使用责任链模式的例子

 该场景描述的是一个待检产品在流水线上检查,产品有两个属性,长度和宽度,流水线上的处理节点也有两个,即长度检查节点和宽度检查节点。为帮助理解,该场景设置的较为简单,实际开发工作中,待检查对象(请求)可以为上下文对象,附带较多属性,同时检查处理节点也可有多个,它们之间是相互独立的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Product {

/**
* 产品长度
*/
Integer length;

/**
* 产品宽度
*/
Integer width;
}

处理器执行链定义,责任链的核心在于此

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

public class ProcessorChain{

// 保存处理节点
private List<Processor> processorList = new ArrayList<>();

// 处理节点下标
private int index = 0;

// 动态扩展处理节点
public ProcessorChain addProcessor(Processor processor) {
processorList.add(processor);
return this;
}

// 获取处理器处理
public boolean process(Product product, ProcessorChain chain) {
if(index == processorList.size()) {
return true;
}
Processor processor = processorList.get(index);
index++;
return processor.process(product, chain);
}

}

处理器接口定义

1
2
3
public interface Processor { 
boolean process(Product request, ProcessorChain chain);
}

        长度检查处理器和宽度检查处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LengthCheckProcessor implements Processor{
@Override
public boolean process(Product request, ProcessorChain chain) {
Integer length = request.getLength();
if (length < 100 && length > 50) {
System.out.println("产品长度检验通过");
return chain.process(request, chain);
}
// 产品长度未检验通过
System.out.println("产品长度未检验通过");
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class WidthCheckProcessor implements Processor{
@Override
public boolean process(Product request, ProcessorChain chain) {
Integer width = request.getWidth();
if (width < 100 && width > 50) {
System.out.println("产品宽度检验通过");
return chain.process(request, chain);
}
// 产品宽度未检验通过
System.out.println("产品宽度未检验通过");
return false;
}
}

        客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {
public static void main(String[] args) {
int[][] arrays = {{60, 60}, {40, 40}, {40, 60}, {60, 40}};
for (int[] array : arrays) {
ProcessorChain processorChain = new ProcessorChain();
processorChain.addProcessor(new LengthCheckProcessor());
processorChain.addProcessor(new WidthCheckProcessor());

Product product = new Product(array[0], array[1]);
boolean checkResult = processorChain.process(product, processorChain);
if(checkResult) {
System.out.println("产品最终检验合格");
} else {
System.out.println("产品最终检验不合格");
}
System.out.println();
}

}
}

1 使用责任链模式设计热插拔权限控制

首先创建一个实体类Member。

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 class Member {  
private String loginName;
private String loginPass;
private String roleName;

public Member(String loginName, String loginPass) {
this.loginName = loginName;
this.loginPass = loginPass;
}

public String getLoginName() {
return loginName;
}

public String getLoginPass() {
return loginPass;
}

public String getRoleName() {
return roleName;
}

public void setRoleName(String roleName) {
this.roleName = roleName;
}

@Override
public String toString() {
return "Member{" +
"loginName='" + loginName + '\'' +
", loginPass='" + loginPass + '\'' +
'}';
}
}

然后来看一段我们经常写的代码。

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 class MemberService {  

public void login(String loginName,String loginPass){
if(StringUtils.isEmpty(loginName) ||
StringUtils.isEmpty(loginPass)){
System.out.println("用户名和密码校验成功,可以往下执行");
return; }
System.out.println("用户名和密码不为空,可以往下执行");

Member member = checkExists(loginName,loginPass);
if(null == member){
System.out.println("用户不存在");
return; }
System.out.println("登录成功!");

if(!"管理员".equals(member.getRoleName())){
System.out.println("您不是管理员,没有操作权限");
return; }
System.out.println("允许操作");

}

private Member checkExists(String loginName,String loginPass){
Member member = new Member(loginName,loginPass);
member.setRoleName("管理员");
return member;
}

public static void main(String[] args) {
MemberService service = new MemberService();
service.login("tom","666");
}

}

在上面代码中,主要做了登录前的数据验证。其判断逻辑是有先后顺序的。首先做非空判断,然后检查账号是否有效,最终获得用户角色。根据用户角色所拥有的权限匹配是否有操作权限。那么这样的检验性代码一般都是必不可少的,但是写在具体的业务代码中又显得非常臃肿,因此可以用责任链模式,将这些检查步骤串联起来,而且不影响代码美观,可以使我们在编码时更加专注于某一个具体的业务逻辑处理。 下面用责任链模式来优化代码,首先创建一个Handler类。

1
2
3
4
5
6
7
8
9
10
public abstract class Handler {  
protected Handler chain;

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

public abstract void doHandle(Member member);

}

然后分别创建非空校验ValidateHandler类、登录校验LoginHandler类和权限校验AuthHandler类。ValidateHandler类的代码如下。

1
2
3
4
5
6
7
8
9
10
public class ValidateHandler extends Handler {  
public void doHandle(Member member) {
if(StringUtils.isEmpty(member.getLoginName()) ||
StringUtils.isEmpty(member.getLoginPass())){
System.out.println("用户名或者密码为空");
return; }
System.out.println("用户名和密码校验成功,可以往下执行");
chain.doHandle(member);
}
}

LoginHandler类的代码如下。

1
2
3
4
5
6
7
8
public class LoginHandler extends Handler {  

public void doHandle(Member member) {
System.out.println("登录成功!");
member.setRoleName("管理员");
chain.doHandle(member);
}
}

AuthHandler类的代码如下。

1
2
3
4
5
6
7
8
9
public class AuthHandler extends Handler {  

public void doHandle(Member member) {
if(!"管理员".equals(member.getRoleName())){
System.out.println("您不是管理员,没有操作权限");
return; }
System.out.println("您是管理员,允许操作");
}
}

接着修改MemberService中的代码,其实只需要将前面定义好的几个Handler根据业务需求串联起来,形成一条链即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MemberService {
public void login(String loginName,String loginPass){
Handler validateHandler = new ValidateHandler();
Handler loginHandler = new LoginHandler();
Handler authHandler = new AuthHandler();

validateHandler.next(loginHandler);
loginHandler.next(authHandler);

validateHandler.doHandle(new Member(loginName,loginPass));
}

}

最后编写客户端调用代码。

1
2
3
4
5
6
public class Test {  
public static void main(String[] args) {
MemberService service = new MemberService();
service.login("tom","666");
}
}

其实我们平时使用的很多权限校验框架都是运用这个原理的,将各个维度的权限处理解耦之后再串联起来,只处理各自相关的职责。如果职责与自己不相关,则抛给链上的下一个Handler,俗称“踢皮球”。

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

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

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

public abstract void doHandle(Member member);

public static class Builder<T> {
private Handler<T> head;
private Handler<T> tail;

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

return this; }

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

}

然后修改MemberService的代码。

1
2
3
4
5
6
7
8
9
10
11
12
public class MemberService {  
public void login(String loginName,String loginPass){

Handler.Builder builder = new Handler.Builder();
builder.addHandler(new ValidateHandler())
.addHandler(new LoginHandler())
.addHandler(new AuthHandler());

builder.build().doHandle(new Member(loginName,loginPass));

}
}

因为建造者模式要构建的是节点处理者,所以我们把Builder作为Handler的静态内部类,并且因为客户端不需要进行链式组装,所以还可以把链式组装方法next()方法设置为private,使Handler更加高聚合,代码如下。

1
2
3
4
5
6
7
public abstract class Handler<T> {  
protected Handler chain;

private void next(Handler handler){
this.chain = handler;
}
}

责任链的2种实现

节点传递方式

节点传递方式也就是,责任链中当前节点处理完成之后,自己传递给下一个处理节点继续处理。

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 interface Handler {
default boolean match(String msg) {
return true;
}
void process(String msg);
}

public abstract class AbstractHandler implements Handler {
private Handler next;

public AbstractHandler setNextHandler(Handler next) {
this.next = next;
return this;
}

@Override
public void process(String msg) {
doProcess(msg);

if (next != null) {
next.process(msg);
}
}

protected abstract void doProcess(String msg);
}

// 具体的责任链处理器
public class Handler1 extends AbstractHandler {
@Override
public void doProcess(String msg) {
System.out.println("[Handler1] process " + msg);
}
}
public class Handler2 extends AbstractHandler {
@Override
protected void doProcess(String msg) {
System.out.println("[Handler2] process " + msg);
}
}
public class Handler3 extends AbstractHandler {
@Override
protected void doProcess(String msg) {
System.out.println("[Handler3] process " + msg);
}
}

2 统一传递方式

统一传递方式也就是,不由责任链中处理节点传递给下一个节点,而是由统一的传递逻辑进行传递。

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
public class HandlerWrap {
private List<Handler> handlerList = new ArrayList<>();

public HandlerWrap() {
handlerList.add(new Handler1());
handlerList.add(new Handler2());
handlerList.add(new Handler3());
}

public void process(String msg) {
for (Handler handler : handlerList) {
handler.process(msg);
}
}
}

public class Handler1 implements Handler {
@Override
public void process(String msg) {
System.out.println("[Handler1] process " + msg);
}
}
public class Handler2 implements Handler {
@Override
public void process(String msg) {
System.out.println("[Handler2] process " + msg);
}
}
public class Handler3 implements Handler {
@Override
public void process(String msg) {
System.out.println("[Handler3] process " + msg);
}
}

两种实现方式的比较

上述两种实现方式差别就是谁来进行下一个节点的传递工作,节点传递方式 是责任链中当前处理节点处理完成之后,自己传递给下一个节点;统一传递方式 是在统一的地方进行传递工作,减轻处理节点的“负担”。

二者本质上是一样的,不过前一种实现方式初始化成本较高,还要注意处理节点的前后顺序,这种调整一个节点的位置时特别要注意前后节点的关系,否则处理链顺序就错乱了。

后续开发中,建议使用第二种实现方式,这种责任链初始化成本较低,调整责任链成本较小。不过有些责任链使用场景中,会将前一个处理节点的返回结果作为下一个处理节点的入参,这种场景一般推荐使用第一种实现方式,就像Netty中的ChannelHandler处理链流程类似。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!