设计模式6-ChainOfResponsibility

本文最后更新于:3 years ago

责任链模式算是设计模式中比较难的模式了,所以这里会比较详细的介绍一下这个模式。

责任链模式

  顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于 行为型模式

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。结构型模式

如果想了解责任链模式模式的具体的介绍,菜鸟教程介绍得比较详细↓
菜鸟教程-责任链模式

结构图


优缺点

优点:
1、降低耦合度。它将请求的发送者和接收者解耦。
2、简化了对象。使得对象不需要知道链的结构。
3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
4、增加新的请求处理类很方便。

缺点:
1、不能保证请求一定被接收。
2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
3、可能不容易观察运行时的特征,有碍于除错。

使用场景

1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
3、可动态指定一组对象处理请求。

实现代码

提出需求,有一些基础的应该知道,客户端在向服务端发送请求的时候,是存在过滤的(为什么要过滤?为了防止黑客攻击,例如xss)。
而这次我们提出的需求就是,用户在向服务器端提交的时候,我们对提交的东西,进行匹配替换。

简单代码如下:

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
Code1
class Msg{
private String name;
private String msg;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

@Override
public String toString() {
return "Msg{" +
"name='" + name + '\'' +
", msg='" + msg + '\'' +
'}';
}
}

Main
public class Main {
public static void main(String[] args) {
Msg msg = new Msg();
msg.setMsg("插播一条消息<script>:本网站已经被我Hacker了,交钱信息不外漏:),联系方式:xf.com");

//处理msg
String r = msg.getMsg();
r = r.replace('<', '[');
r = r.replace('>', ']');
r = r.replaceAll("Hacker", " ");
msg.setMsg(r);
System.out.println(msg);
}
}

结果如下:

1
Msg{name='null', msg='插播一条消息[script]:本网站已经被我 了,交钱信息不外漏:),联系方式:xf.com'}

但弊端也是很明显的,对于过滤这个动作不确定,可能以后会再添加其他的过滤,这时候该怎么做呢?
第一反应肯定是直接在后面添加过滤语句,这一定是可以的。但是这里的代码其实是很简单,真正的业务可能每一步都很复杂,例如过滤字符串可能要过滤很多
如果将所有代码都写在一块,首先非常乱,其次不容易扩展。
软件可扩展性指添加新功能的时候,对原来的软件架构改动越少,说明扩展性越好
封装->变化(要处理的逻辑,这里是要处理的字符串的逻辑可能会变)

解决思路是,前面学过了这么多模式,不难想出,我们将过滤这个动作封装成一个接口,里面含有过滤方法。
然后将不同的过滤机制实现这个接口

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
Code2
class Msg{
private String name;
private String msg;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

@Override
public String toString() {
return "Msg{" +
"name='" + name + '\'' +
", msg='" + msg + '\'' +
'}';
}
}

interface Filter{
void doFilter(Msg msg);
}

class HTMLFilter implements Filter{

@Override
public void doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replace('<', '[');
r = r.replace('>', ']');
msg.setMsg(r);
}
}

class SensitiveFilter implements Filter{

@Override
public void doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replaceAll("Hacker", " ");
msg.setMsg(r);
}
}

Main
public class Main {
public static void main(String[] args) {
Msg msg = new Msg();
msg.setMsg("插播一条消息<script>:本网站已经被我Hacker了,交钱信息不外漏:),联系方式:xf.com");

//处理msg
List<Filter> filters = new ArrayList<>();
filters.add(new HTMLFilter());
filters.add(new SensitiveFilter());

for (Filter filter : filters) {
filter.doFilter(msg);
}
System.out.println(msg);
}
}

输出

1
Msg{name='null', msg='插播一条消息[script]:本网站已经被我 了,交钱信息不外漏:),联系方式:xf.com'}

现在,这些过滤就可以看出是一条责任链


这里我们再完善一下代码:

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
Code3
class Msg{
private String name;
private String msg;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

@Override
public String toString() {
return "Msg{" +
"name='" + name + '\'' +
", msg='" + msg + '\'' +
'}';
}
}

interface Filter{
void doFilter(Msg msg);
}

class HTMLFilter implements Filter{

@Override
public void doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replace('<', '[');
r = r.replace('>', ']');
msg.setMsg(r);
}
}

class SensitiveFilter implements Filter{

@Override
public void doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replaceAll("Hacker", " ");
msg.setMsg(r);
}
}

FilterChain
class FilterChain{
List<Filter> filters = new ArrayList<>();

/*public void add(Filter filter){
filters.add(filter);
}*/

//这里注意,这个是编程的一个小技巧,这里返回FilterChain的话,
//那么可以将代码写出链式编程:
//fc.add(new HTMLFilter()).add(new SensitiveFilter());
public FilterChain add(Filter filter){
filters.add(filter);
return this;
}

public void doFilter(Msg msg){
for (Filter f :
filters) {
f.doFilter(msg);
}
}
}

Main
public class Main {
public static void main(String[] args) {
Msg msg = new Msg();
msg.setMsg("插播一条消息<script>:本网站已经被我Hacker了,交钱信息不外漏:),联系方式:xf.com");

//处理msg
FilterChain fc = new FilterChain();
/*fc.add(new HTMLFilter());
fc.add(new SensitiveFilter());*/

// 编程小技巧,将返回值改为FilterChain类型
fc.add(new HTMLFilter())
.add(new SensitiveFilter());

System.out.println(msg);
}
}

Code2和Code3的区别并不大,但Code3的代码,实用性更高。

我们知道在过滤的时候肯定不止一条链对于数据的处理,实际上应该是多条链。那如何将这多条链同时来处理一条数据呢?
这里我们让FilterChain也实现Filter接口,这样我们就可以通过add()方法,将多条链链接在一起。
而此时就可以看出Code2和Code3之间的差距了

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
Code4
class Msg{
private String name;
private String msg;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

@Override
public String toString() {
return "Msg{" +
"name='" + name + '\'' +
", msg='" + msg + '\'' +
'}';
}
}

interface Filter{
void doFilter(Msg msg);
}

class HTMLFilter implements Filter{

@Override
public void doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replace('<', '[');
r = r.replace('>', ']');
msg.setMsg(r);
}
}

class SensitiveFilter implements Filter{

@Override
public void doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replaceAll("Hacker", " ");
msg.setMsg(r);
}
}

class FaceFilter implements Filter{

@Override
public void doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replace(":)", "^V^");
msg.setMsg(r);
}
}

class URLFilter implements Filter{

@Override
public void doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replace("xf.com", "https://xf.com");
msg.setMsg(r);
}
}

//class FilterChain{
class FilterChain implements Filter{
List<Filter> filters = new ArrayList<>();

/*public void add(Filter filter){
filters.add(filter);
}*/

public FilterChain add(Filter filter){
filters.add(filter);
return this;
}

public void doFilter(Msg msg){
for (Filter f :
filters) {
f.doFilter(msg);
}
}
}

Main
public class Main {
public static void main(String[] args) {
Msg msg = new Msg();
msg.setMsg("插播一条消息<script>:本网站已经被我Hacker了,交钱信息不外漏:),联系方式:xf.com");

FilterChain fc = new FilterChain();
/*fc.add(new HTMLFilter());
fc.add(new SensitiveFilter());*/

// 编程小技巧,将返回值改为FilterChain类型
fc.add(new HTMLFilter())
.add(new SensitiveFilter());
// 再创建一个Filter链
FilterChain fc2 = new FilterChain();
fc2.add(new FaceFilter()).add(new URLFilter());

//此时完全可以将FileChain也实现Filter接口
fc.add(fc2);
fc.doFilter(msg);
System.out.println(msg);
}

}

这里我们就可以看出Code3就很好的将代码的数量大大减少。

到了现在,只差最后一点需求了,由FilterChain中的某一个Filter决定链条是否继续。
什么意思呢?先看一下图


当数据在Filter2处理完成时,发现含有敏感词,此时就不用Filter3处理就结束了。

但怎么实现呢?这个感觉还是比较复杂的

这个必须要在Filter内部进行过滤,是不能够写在FilterChain中的(影响扩展)
这里要将Filter接口中的 doFilter() 方法返回类型改为Boolean类型,那么在每一个实现Filter的方法中,
都需要返回Boolean值,然后判断如果为false,即退出。

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
Code5
class Msg{
private String name;
private String msg;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

@Override
public String toString() {
return "Msg{" +
"name='" + name + '\'' +
", msg='" + msg + '\'' +
'}';
}
}

interface Filter{
Boolean doFilter(Msg msg);
}

class HTMLFilter implements Filter{

@Override
public Boolean doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replace('<', '[');
r = r.replace('>', ']');
msg.setMsg(r);
return true;
}
}

class SensitiveFilter implements Filter{

@Override
public Boolean doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replaceAll("Hacker", " ");
msg.setMsg(r);
return false;
}
}

class FaceFilter implements Filter{

@Override
public Boolean doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replace(":)", "^V^");
msg.setMsg(r);
return true;
}
}

class URLFilter implements Filter{

@Override
public Boolean doFilter(Msg msg) {
String r = msg.getMsg();
r = r.replace("xf.com", "https://xf.com");
msg.setMsg(r);
return true;
}
}

//class FilterChain{
class FilterChain implements Filter{
List<Filter> filters = new ArrayList<>();

/*public void add(Filter filter){
filters.add(filter);
}*/

public FilterChain add(Filter filter){
filters.add(filter);
return this;
}

public Boolean doFilter(Msg msg){
for (Filter f :
filters) {
if (!f.doFilter(msg)) return false;
}
return true;
}
}


public class Main {
public static void main(String[] args) {
Msg msg = new Msg();
msg.setMsg("插播一条消息<script>:本网站已经被我Hacker了,交钱信息不外漏:),联系方式:xf.com");

FilterChain fc = new FilterChain();
/*fc.add(new HTMLFilter());
fc.add(new SensitiveFilter());*/

// 编程小技巧,将返回值改为FilterChain类型
fc.add(new HTMLFilter())
.add(new SensitiveFilter());
// 再创建一个Filter链
FilterChain fc2 = new FilterChain();
fc2.add(new FaceFilter()).add(new URLFilter());

//此时完全可以将FileChain也实现Filter接口
fc.add(fc2);
fc.doFilter(msg);
System.out.println(msg);
}

}

如果将SensitiveFilter类中返回值为true,则结果为:

1
Msg{name='null', msg='插播一条消息[script]:本网站已经被我 了,交钱信息不外漏^V^,联系方式:https://xf.com'}

如果将SensitiveFilter类中返回值为false,则结果为:

1
Msg{name='null', msg='插播一条消息[script]:本网站已经被我 了,交钱信息不外漏:),联系方式:xf.com'}

是不是观察出来什么不一样了,当SensitiveFilter类中返回false,那么此时就不会再去调用fc2中的Filter。

好了,到这里责任链模式,思想就基本上🔒完了,如果想比较深入了解的话,可以去看看Java EE中FilterChain的API,如果还有哪天比较空闲的话,我会把这个API的实现说一下,毕竟这也算是设计模式中难度第二的模式。


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