GeekIBLi

设计模式之美-责任链模式

2021-08-07

责任链模式

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

类图

Handler 责任链抽象类 链式结构

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

protected Handler nextHandler;

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

public abstract void handleRequest(String request);
}

ConcreteHandlerA 节点对象A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ConcreteHandlerA extends Handler {

@Override
public void handleRequest(String request) {
if ("reqA".equals(request)) {
System.err.println(this.getClass().getSimpleName() + "deal this request " + request);
return;
}
if (this.nextHandler != null) {
this.nextHandler.handleRequest(request);
}
}
}

ConcreteHandlerB 节点对象B

1
2
3
4
5
6
7
8
9
10
11
12
public class ConcreteHandlerB extends Handler {
@Override
public void handleRequest(String request) {
if ("reqB".equals(request)) {
System.err.println(this.getClass().getSimpleName() + "deal this request " + request);
return;
}
if (this.nextHandler != null) {
this.nextHandler.handleRequest(request);
}
}
}

DemoTest 测试类

1
2
3
4
5
6
7
8
9
10
public class DemoTest {
public static void main(String[] args) {
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
handlerA.setNextHandler(handlerB);
handlerA.handleRequest("reqB");
}
}
// 输出结果:
//ConcreteHandlerBdeal this request reqB

业务

下面我们举一个业务场景的例子🌰来展示一下什么是责任链模式:
我们登录时,肯定要校验用户名和密码有没有传参,如果参数都是空的话,那也没有必要执行后面的登录部分,直接返回了;如果参数是合法的,那我们就要校验数据库是否存在这个用户了,如果不存在该用户,那就直接返回;如果存在,那就要校验用户有没有权限访问正在请求的资源,如果有权限,则可以正常访问资源,如果没有权限,则抛出异常;

非责任链模式写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 以下是伪代码
public void login(String userName,String pwd){
if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(pwd)){
throw new IllegalArgumentException();
}

Member member = selectUser(userName,pwd);
if (Objects.isNull(member)){
throw new IllegalArgumentException();
}

if (!validateRoot(member)){
throw new IllegalArgumentException();
}
System.err.println("Login success!");
}

这种写法虽然简单,但是将所有的操作全部杂糅在一起了,结构是十分混乱的;
下面👇看一下如果使用责任链模式该如何实现:

Handler 抽象责任链处理器

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

protected Handler next;

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

public abstract void doHandler(Member member);
}

ValidateHandler 参数校验处理器

1
2
3
4
5
6
7
8
9
10
11
public class ValidateHandler extends Handler{
@Override
public void doHandler(Member member) {
if (StringUtils.isEmpty(member.getLoginName()) || StringUtils.isEmpty(member.getLoginPass())){
System.err.println("登录名或者密码为空!");
return;
}
System.err.println("参数校验成功!");
next.doHandler(member);
}
}

登录处理器

1
2
3
4
5
6
7
8
public class LoginHandler extends Handler {
@Override
public void doHandler(Member member) {
System.err.println("login success!");
member.setRoleName("root");
next.doHandler(member);
}
}

鉴权处理器

1
2
3
4
5
6
7
8
9
10
public class AuthHandler extends Handler{
@Override
public void doHandler(Member member) {
if ("root".equals(member.getRoleName())){
System.err.println("Auth success!");
return;
}
System.err.println("Auth failed!");
}
}

Member成员类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Member {

private String loginName;
private String loginPass;
private String roleName;

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

// GETTER SETTER ....
}

DemoTest 测试类

1
2
3
4
5
6
7
8
9
10
11
12
public class DemoTest {
// 首先创建所有的处理器,设置后它们的先后顺序,设置next节点,完成之后在执行链路操作;
public static void main(String[] args) {
ValidateHandler validateHandler = new ValidateHandler();
LoginHandler loginHandler = new LoginHandler();
validateHandler.next(loginHandler);
AuthHandler authHandler = new AuthHandler();
loginHandler.next(authHandler);
Member member = new Member("xiaoming", "222");
validateHandler.doHandler(member);
}
}

上面的写法,看起来有没有比非责任链写法要B格高一些,但是,这样每次创建节点对象,好像又显得不太爽,有些臃肿的意思!而且调整节点之间的顺序时也比较复杂,容易改错! 那我们就进一步优化以下吧!

责任链模式+建造者模式 优化

添加建造器builder

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
public abstract class Handler<T> {

protected Handler next;

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

public abstract void doHandler(Member member);

// 暴露建造器,将节点对象以链表的形式串起来
public static class Builder<T> {
private Handler<T> head;
private Handler<T> tail;

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

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;
}
}
}

测试

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
Member member = new Member("xiaoming", null);
Handler.Builder<Handler> builder = new Handler.Builder();
builder.addHandler(new ValidateHandler())
.addHandler(new LoginHandler())
.addHandler(new AuthHandler())
.build()
.doHandler(member);
}

通过添加建造器之后,责任链的组装与调用是不是显得很清晰,每个节点对象各司其职,如果需要本节点执行,则执行,如果不是,则交给下一个节点继续!

责任链适用的场景

  • 多个对象处理同一个请求,但具体由那个对象处理则在运动时动态决定
  • 在不明确🈯指定接受者的情况下,向多个对象的一个提交请求
  • 可以动态指定一组对象处理请求

举一些实际的例子:
javax.servlet.Filter的doFilter方法就是使用的责任链模式;

org.springframework.web.filter.CompositeFilter#doFilter

1
2
3
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
(new CompositeFilter.VirtualFilterChain(chain, this.filters)).doFilter(request, response);
}

这是Spring框架中的一个实现(javax.servlet.Filter),下面展开具体的代码:

org.springframework.web.filter.CompositeFilter

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
public class CompositeFilter implements Filter {
//Filter 这里的Filter相当于处理节点,存放在List中,和我们上面的Handler存放在建造者的链式结构中,异曲同工。
private List<? extends Filter> filters = new ArrayList();

public CompositeFilter() {
}

public void setFilters(List<? extends Filter> filters) {
this.filters = new ArrayList(filters);
}

public void init(FilterConfig config) throws ServletException {
Iterator var2 = this.filters.iterator();

while(var2.hasNext()) {
Filter filter = (Filter)var2.next();
filter.init(config);
}

}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
(new CompositeFilter.VirtualFilterChain(chain, this.filters)).doFilter(request, response);
}

public void destroy() {
int i = this.filters.size();

while(i-- > 0) {
Filter filter = (Filter)this.filters.get(i);
filter.destroy();
}

}

private static class VirtualFilterChain implements FilterChain {
private final FilterChain originalChain;
private final List<? extends Filter> additionalFilters;
private int currentPosition = 0;

public VirtualFilterChain(FilterChain chain, List<? extends Filter> additionalFilters) {
this.originalChain = chain;
this.additionalFilters = additionalFilters;
}

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.currentPosition == this.additionalFilters.size()) {
this.originalChain.doFilter(request, response);
// 通过currentPosition的移动,转移到链路上的不同处理节点,这就是责任链模式的体现
} else {
++this.currentPosition;
Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
nextFilter.doFilter(request, response, this);
}

}
}
}

像上面的例子还有很多很多,比如Spring Security和Shiro中的拦截器就是很典型的责任链模式,还有Netty中的 io.netty.channel.ChannelPipeline等等,都是责任链模式;

责任链模式的优点👍👍👍

  • 将请求与处理解耦
  • 请求处理者(节点对象)只需要关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转发给下一个节点对象
  • 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待处理请求结果
  • 链路结构灵活,可以通过改变链路结果动态的新增和删除责任
  • 易于扩展新的请求节点(符合开闭原则)

责任链模式的缺点

  • 责任链太长或者处理时间太长,导致系统性能下降
  • 如果节点对象存在循环♻️引用时,会造成死循环,导致系统崩溃,所以这个在设计的时候一定要注意不能形成闭环⚠️⚠️⚠️