0%

GoF设计模式(十七):Mediator Pattern 中介者模式

Mediator Pattern中介者模式,作为行为型设计模式的一种。其通过中介者实现了对各对象之间复杂的调用、关联关系的解耦,使之整体表现为松耦合的状态

abstract.jpeg

现实世界的指引

就租房这件事来说,一般会有若干个租房者、若干个出租者。之前租房市场还不发达,每个租房者需要亲自和各个出租者进行对接、沟通。显然这种模式效率并不高。租房者和出租者需要两两之间建立连接。两个群体(租房群体、出租群体)的联系呈现出一种复杂的网状结构。后来房屋中介应运而生,租房者和出租者相互不再需要直接建立连接、沟通,而是都只和房屋中介进行沟通。这样就大大减轻了租房者、出租者各自的沟通成本。从而形成了以房屋中介为中心的星型结构

模式思想

在上面租房的例子中,我们通过引入房屋中介实现了将原有复杂的网状结构转换为以中介为中心的星型结构。这一宝贵的历史经验对于我们软件工程领域同样具有重大的历史意义。当系统中存在多个对象之间存在复杂的调用、关联关系、彼此之间存在复杂的网状关系时,即可以考虑通过引入中介者这一角色实现系统的松耦合。此举将一改原有对象需要持有其他各对象实例引用的复杂局面,使得各对象只需要持有中介者的引用即可,即只和中介者进行交互。而在中介者实例中,一方面其会持有所有对象实例的引用;另一方面其负责统一描述、封装各对象之间的交互关系。这一历史经验被总结为Mediator Pattern中介者模式 。在中介者模式下,其有以下几个角色

  • 抽象同事角色:其是对具体同事角色的抽象定义,定义了一些通用的方法接口
  • 具体同事角色:其是抽象同事角色的具体实现。其内部通过持有具体中介者角色的实例,实现与具体中介者角色的交互
  • 抽象中介者角色:其定义了具体同事角色与中介者之间进行交互的方法
  • 具体中介者角色:该角色则是抽象中介者角色的具体实现,其内部会持有所有具体同事角色的实例,以实现某个具体同事角色实例与其他同事角色实例的交互、联系

实现

一般项目中,我们通常会同时使用多种数据库,比如用于存储的MySQL,用于缓存服务的Redis,用于检索的ElasticSearch等等(严格来说ElasticSearch应该是一种数据检索中间件)。出于数据一致性的考虑,当某个数据库数据变动时(添加、删除数据),其他数据库需要进行同步操作,这里我们规定此三种数据库之间的数据同步规则

  • 当某数据库添加数据时,其他各数据库均需添加相应数据
  • 当MySQL删除数据时,Redis、ElasticSearch均需删除相应数据
  • 当Redis删除数据时,ElasticSearch需删除相应数据
  • 当ElasticSearch删除数据时,Redis需删除相应数据

如果不使用中介者模式,则数据同步操作就需要各数据库之间两两建立联系,相互持有引用来实现。非常不利于后期维护、拓展。而引入中介者模式就可以很好的解决这个问题,各数据库之间无需知道对方的存在,他们只负责和中介者打交道。而将数据同步操作统一在中介者中。当然中介者是需要知道各数据库的

figure 1.jpeg

好了,现在让我们来实现这个例子。以便大家更好的理解该模式的精髓。首先,我们定义一个抽象同事角色——即Database数据库类,其中定义各数据库通用的添加、移除、查看数据的操作

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 Database {
/**
* 中介者
*/
private Mediator mediator;

public Database(Mediator mediator) {
this.mediator = mediator;
}

public Mediator getMediator() {
return mediator;
}

/**
* 向本数据库添加数据, 并通过中介者向其它数据库发送数据同步添加的请求
* @param str
*/
abstract public void add(String str);

/**
* 向本数据库添加数据
* @param str
*/
abstract public void addData(String str);

/**
* 从本数据库移除数据, 并通过中介者向其它数据库发送数据同步移除的请求
* @param str
*/
abstract public void remove(String str);

/**
* 从本数据库移除数据
* @param str
*/
abstract public void removeData(String str);

/**
* 显示本数据库中的数据
*/
abstract public void showData();
}

然后来实现各个具体同事角色——即MySQL、Redis、ElasticSearch等数据库。可以看到,当某个数据库自己的数据发生变动时,会请求中介者进行同步操作(调用syncAddData、syncRemoveData方法)

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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/**
* 具体同事角色:MySQL数据库
*/
public class MySQL extends Database {
/**
* 数据库名称:MySQL数据库
*/
private String name = "MySQL";

/**
* 使用List集合存储数据 模拟 数据库存储数据
*/
private List<String> list = new LinkedList<>();

public MySQL(Mediator mediator) {
super(mediator);
}

/**
* 向本数据库添加数据, 并通过中介者向其它数据库发送数据同步添加的请求
* @param str
*/
@Override
public void add(String str) {
// 向本数据库添加数据
addData(str);
// 通过中介者向其它数据库发送数据同步添加的请求
getMediator().syncAddData(str, name);
}

/**
* 向本数据库添加数据
* @param str
*/
@Override
public void addData(String str) {
list.add(str);
}

/**
* 从本数据库移除数据, 并通过中介者向其它数据库发送数据同步移除的消息
* @param str
*/
@Override
public void remove(String str) {
// 从本数据库移除数据
removeData(str);
// 通过中介者向其它数据库发送数据同步移除的请求
getMediator().syncRemoveData(str, name);
}

/**
* 从本数据库移除数据
* @param str
*/
@Override
public void removeData(String str) {
list.remove(str);
}

/**
* 显示本数据库中的数据
*/
@Override
public void showData() {
String str = "<"+ name+ "> : " + list.toString();
System.out.println(str);
}
}
...
/**
* 具体同事角色:Redis数据库
*/
public class Redis extends Database {
/**
* 数据库名称:Redis数据库
*/
private String name = "Redis";

/**
* 使用List集合存储数据 模拟 数据库存储数据
*/
private List<String> list = new LinkedList<>();

public Redis(Mediator mediator) {
super(mediator);
}

/**
* 向本数据库添加数据, 并通过中介者向其它数据库发送数据同步添加的请求
* @param str
*/
@Override
public void add(String str) {
// 向本数据库添加数据
addData(str);
// 通过中介者向其它数据库发送数据同步添加的请求
getMediator().syncAddData(str, name);
}

/**
* 向本数据库添加数据
* @param str
*/
@Override
public void addData(String str) {
list.add(str);
}

/**
* 从本数据库移除数据, 并通过中介者向其它数据库发送数据同步移除的消息
* @param str
*/
@Override
public void remove(String str) {
// 从本数据库移除数据
removeData(str);
// 通过中介者向其它数据库发送数据同步移除的请求
getMediator().syncRemoveData(str, name);
}

/**
* 从本数据库移除数据
* @param str
*/
@Override
public void removeData(String str) {
list.remove(str);
}

/**
* 显示本数据库中的数据
*/
@Override
public void showData() {
String str = "<"+ name+ "> : " + list.toString();
System.out.println(str);
}
}
...
/**
* 具体同事角色:ElasticSearch数据库
*/
public class ElasticSearch extends Database {
/**
* 数据库名称:ElasticSearch数据库
*/
private String name = "ElasticSearch";

/**
* 使用List集合存储数据 模拟 数据库存储数据
*/
private List<String> list = new LinkedList<>();

public ElasticSearch(Mediator mediator) {
super(mediator);
}

/**
* 向本数据库添加数据, 并通过中介者向其它数据库发送数据同步添加的请求
* @param str
*/
@Override
public void add(String str) {
// 向本数据库添加数据
addData(str);
// 通过中介者向其它数据库发送数据同步添加的请求
getMediator().syncAddData(str, name);
}

/**
* 向本数据库添加数据
* @param str
*/
@Override
public void addData(String str) {
list.add(str);
}

/**
* 从本数据库移除数据, 并通过中介者向其它数据库发送数据同步移除的消息
* @param str
*/
@Override
public void remove(String str) {
// 从本数据库移除数据
removeData(str);
// 通过中介者向其它数据库发送数据同步移除的请求
getMediator().syncRemoveData(str, name);
}

/**
* 从本数据库移除数据
* @param str
*/
@Override
public void removeData(String str) {
list.remove(str);
}

/**
* 显示本数据库中的数据
*/
@Override
public void showData() {
String str = "<"+ name+ "> : " + list.toString();
System.out.println(str);
}
}

现在,就轮到我们的中介者角色登场了。首先定义一个抽象中介者角色——Mediator类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 抽象中介者角色
*/
public abstract class Mediator {

/**
* 同步添加数据
* @param str 需同步的数据
* @param name 请求者
*/
abstract void syncAddData(String str, String name);

/**
* 同步删除数据
* @param str 需同步的数据
* @param name 请求者
*/
abstract void syncRemoveData(String str, String name);
}

然后实现一个用于数据同步的具体中介者,即DataSyncMediator类。可以看到,其内部持有各数据库的实例引用,并在syncAddData、syncRemoveData方法中统一实现具体的数据同步规则

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 DataSyncMediator extends Mediator {
private MySQL mySQL;
private Redis redis;
private ElasticSearch elasticSearch;

public void setMySQL(MySQL mySQL) {
this.mySQL = mySQL;
}

public void setReis(Redis redis) {
this.redis = redis;
}

public void setElasticSearch(ElasticSearch elasticSearch) {
this.elasticSearch = elasticSearch;
}

@Override
void syncAddData(String str, String name) {
// 添加数据时, 其它各数据库均需进行同步添加
switch (name) {
case "MySQL":
redis.addData(str);
elasticSearch.addData(str);
break;
case "Redis":
mySQL.addData(str);
elasticSearch.addData(str);
break;
case "ElasticSearch":
mySQL.addData(str);
redis.addData(str);
break;
}
}

@Override
void syncRemoveData(String str, String name) {
switch (name) {
// MySQL数据库移除数据时,其它各数据库均需移除相应数据
case "MySQL":
redis.removeData(str);
elasticSearch.removeData(str);
break;
// Redis数据库移除数据时,则只有ElasticSearch数据库需移除相应数据
case "Redis":
elasticSearch.removeData(str);
break;
// ElasticSearch数据库移除数据时,则只有Redis数据库需移除相应数据
case "ElasticSearch":
redis.removeData(str);
break;
}
}
}

好了,现在让我们来实际测试下这个Demo

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
/**
* Mediator Pattern 中介者模式 Demo
*/
public class MediatorPatternDemo {
public static void main(String[] args) {
// 构造数据同步中介者
DataSyncMediator dataSyncMediator = new DataSyncMediator();

// 构造各数据库, 且各数据库只需"知道"中介者
MySQL mySQL = new MySQL(dataSyncMediator);
Redis redis = new Redis(dataSyncMediator);
ElasticSearch elasticSearch = new ElasticSearch(dataSyncMediator);

// 中介者需要"知道"全部数据库
dataSyncMediator.setMySQL( mySQL );
dataSyncMediator.setReis( redis );
dataSyncMediator.setElasticSearch( elasticSearch );

System.out.println("--------------- Test 1: MySQL添加数据: Aaron ---------------");
mySQL.add("Aaron"); // MySQL添加数据
// 查看各数据库中的数据
mySQL.showData();
redis.showData();
elasticSearch.showData();

System.out.println("\n--------------- Test 2: Redis添加数据: Tony ---------------");
redis.add("Tony"); // Redis添加数据
// 查看各数据库中的数据
mySQL.showData();
redis.showData();
elasticSearch.showData();

System.out.println("\n--------------- Test 3: ElasticSearch添加数据: Bob,Amy,David ---------------");
elasticSearch.add("Bob"); // ElasticSearch添加数据
elasticSearch.add("Amy");
elasticSearch.add("David");
// 查看各数据库中的数据
mySQL.showData();
redis.showData();
elasticSearch.showData();

System.out.println("\n--------------- Test 4: MySQL移除数据: Amy ---------------");
mySQL.remove("Amy"); // MySQL移除数据
// 查看各数据库中的数据
mySQL.showData();
redis.showData();
elasticSearch.showData();

System.out.println("\n--------------- Test 5: Redis移除数据: Aaron ---------------");
redis.remove("Aaron"); // Redis移除数据
// 查看各数据库中的数据
mySQL.showData();
redis.showData();
elasticSearch.showData();

System.out.println("\n--------------- Test 6: ElasticSearch移除数据: Tony ---------------");
elasticSearch.remove("Tony"); // ElasticSearch移除数据
// 查看各数据库中的数据
mySQL.showData();
redis.showData();
elasticSearch.showData();
}
}

测试结果如下,符合预期。当然对于中介者模式而言,其缺点也是显然易见的。由于中介者负责实现、维护各对象实例之间的交互规则,所以会导致中介者类代码急剧膨胀复杂

figure 2.jpeg

参考文献

  1. Head First 设计模式 弗里曼著
请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝