从一张关于Excel文件导入的故事卡谈起

在9月份的时候,还有一件事让我觉得很有收获

在领到一张关于excel导入的故事卡时候,我像往常一样开始找文军老师 pair coding

故事卡的内容主要是要将excel的内容进行批量导入,并且导入后还要进行判空校验格式校验业务场景校验(用户是否存在、用户A、B是否存在关系)重复数据校验以及最后的组装等操作

区别于以往pair时我们先写测试的惯例,文军老师那天突然问我

“你知不知道责任链?”

“学设计模式的时候好像听说过,但是也没有实践过”

文军老师看出了我不会的尴尬,随即开始讲起了什么是责任链

责任链设计模式

从wiki上的介绍来看,责任链主要是

In object-oriented design, the chain-of-responsibility pattern is a behavioral design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain.

概括的来说,其实是这三点内容:

1. 有一个待处理的请求
1. 有一系列的对象可以对这个请求进行处理
1. 每一个对象都有自己的上下文,彼此互不干涉。处理完后,交由下一个对象来处理。

流水线

我们还是先以一个例子来进行说明

客户输入一个request,我们要对这个request进行处理,并将处理好的结果打印出来

例子

首先定义一个Request对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.List;

public class Request {
private List<String> operations;

public Request(List<String> operation) {
this.operations = operation;
}

public List<String> getOperations() {
return operations;
}

public void setOperation(String operation) {
this.operations.add(operation);
}
}

然后我们不择手段的先写一个相应的处理实现方法,就写在main方法里面(为了方便演示,具体内容就省略了😂)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package ChainOfReposibility;

import java.util.ArrayList;
import java.util.Collections;

public class Main {

public static void main(String[] args) {
Request request = new Request(new ArrayList<>(Collections.singletonList("This is a request")));

// doing some check operations above request is null
request.setOperation("request is not null");

// doing some check operations above request is legal
request.setOperation("request is legal");

// doing some command operations
request.setOperation("command build completed");

request.getOperations().forEach(System.out::println);

}
}

得到的结果:

1
2
3
4
5
> output:
This is a request
request is not null
request is legal
command build completed

这样写的好处是简单直接,但坏处显而易见。违反了SOLID 原则上的单一职责和开闭和原则,很难进行扩展

于是我们可以尝试将不同的处理步骤进行抽取方法,重构后得到下面的代码:

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
package ChainOfReposibility;

import java.util.ArrayList;
import java.util.Collections;

public class Main {

public static void main(String[] args) {
Request request = new Request(new ArrayList<>(Collections.singletonList("This is a request")));

checkRequestIsNull(request);

checkRequestlegal(request);

buildRequestToCommand(request);

request.getOperations().stream()
.forEach(System.out::println);

}

private static void buildRequestToCommand(Request request) {
// doing some command operations
request.setOperation("command build completed");
}

private static void checkRequestlegal(Request request) {
// doing some check operations above request is legal
request.setOperation("request is legal");
}

private static void checkRequestIsNull(Request request) {
// doing some check operations above request is null
request.setOperation("request is not null");
}

}

此时,开闭合原则其实已经解决了,需要加任何扩展或者删除任何方法都变的比之前更加灵活了

但是我们仔细观察后可以发现,其实这里面每个处理Request的对象都只做了一件事情,就是处理本身而已

那我们能不能规定一个接口,并将这些方法分别Delegate到各自的类中去并且让这些类都实现我们定义的接口呢?

于是,责任链的原型就被我们重构出来了:

⚠️Warning

以下内容存在分歧,责任链的定义和解释建议参考:chain-of-responsibility

1
2
3
4
5
package ChainOfResponsibility;

public interface Handler<T> {
void process(T request);
}

三个类,举其中一个做例子:

1
2
3
4
5
6
7
8
9
10
package ChainOfResponsibility;

public class CheckRequestIsNullHandler implements Handler<Request> {

@Override
public void process(Request request) {
// doing some check operations above request is null
request.setOperation("request is not null");
}
}

最后,再新建一个类用来触发执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package ChainOfResponsibility;

import java.util.ArrayList;
import java.util.List;

public class ChainOfResponsibility implements Handler<Request> {

private List<Handler> chains = new ArrayList<>();

public ChainOfResponsibility nextHandler (Handler handler) {
chains.add(handler);
return this;
}

@Override
public void process(Request request) {
chains.forEach(handler -> handler.process(request));
}
}

使用责任链后的Main类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package ChainOfResponsibility;

import java.util.ArrayList;
import java.util.Collections;

public class Main {

public static void main(String[] args) {
// 准备请求
Request request = new Request(new ArrayList<>(Collections.singletonList("This is a request")));
// 组装
ChainOfResponsibility chainOfResponsibility = new ChainOfResponsibility();
chainOfResponsibility
.nextHandler(new CheckRequestIsNullHandler())
.nextHandler(new CheckRequestLegalHandler())
.nextHandler(new BuildRequestToCommandHandler());
// 统一执行
chainOfResponsibility.process(request);
// 打印结果
request.getOperations().forEach(System.out::println);

}

}

模型如下图所示:

责任链UML图

结论

使用责任链模式的好处明显能够看出,比如

  • 满足SOLID 原则
  • 发送请求方和接受请求方解耦
  • 易于扩展(尤其是结合范型后,扩展性大大增强)

但是,其缺点也很明显,比如:

  • 链式数据结构必然会牺牲一定的效率
  • 不保证被接受,正因为上述的解耦的优点,同时也会产生新的问题,即请求没有明确的接收者,那就不保证一定会被处理。
  • 由于责任链的链式结构,使得每一个节点对象处理完后都只能被下一个节点处理,并不能根据条件判断进行多个候选节点的分发。不过,针对这个问题,衍生出来的责任树方法提供了一种解决方案,感兴趣的同学可以看看。
  • 同样由于每一个处理对象只负责自己职责的事情,对之前和之后的处理并不关心,使得之后的问题排查变得更加困难。

最后,依然感谢团队TL成哥和Coach文军老师提供的各种帮助。以及团队中其他小伙伴的支持,比心♥️