摘要
JDK:1.8.0_202
# 一:场景问题
# 1.1 如果没有主板
电脑里面各个配件之间的交互,主要是通过主板来完成的。试想一下,如果电脑里面没有主板,会怎样呢?
如果电脑里面没有了主板,那么各个配件之间就必须自行相互交互,以互相传送数据,理论上说,基本上各个配件相互之间都存在交互数据的可能。如图所示:
由于各个配件的接口不同,那么相互之间交互的时候,还必须把数据接口进行转换才能匹配上,那就更恐怖了。
所幸是有了主板,各个配件的交互完全通过主板来完成,每个配件都只需要和主板交互,而主板知道如何和所有的配件打交道,那就简单多了,这就避免了如上图所描述的那样乱作一团,有主板后的结构如图所示:
# 1.2 有何问题
如果上面的情况发生在软件开发上呢?
如果把每个电脑配件都抽象成为一个类或者是子系统,那就相当于出现了多个类之间相互交互,而且交互还很繁琐,导致每个类都必须知道所有需要交互的类,也就是我们常说的类和类耦合了,是不是很麻烦?
在软件开发中出现这种情况可就不妙了,不但开发的时候每个类会复杂,因为要兼顾其它的类,更要命的是每个类在发生改动的时候,需要通知所有相关的类一起修改,因为接口或者是功能发生了变动,使用它的地方都得变,是不是显得特别繁琐!
那该如何来简化这种多个对象之间的交互呢?
# 1.3 使用电脑来看电影
为了演示,考虑一个稍微具体点的功能。在日常生活中,我们经常使用电脑来看电影,把这个过程描述出来,这里仅仅考虑正常的情况,也就是有主板的情况,简化后假定会有如下的交互过程:
首先是光驱要读取光盘上的数据,然后告诉主板,它的状态改变了;
主板去得到光驱的数据,把这些数据交给CPU进行分析处理;
CPU处理完后,把数据分成了视频数据和音频数据,通知主板,它处理完了;
主板去得到CPU处理过后的数据,分别把数据交给显卡和声卡,去显示出视频和发出声音;
当然这是一个持续的、不断重复的过程,从而形成不间断的视频和声音,具体的运行过程不在讨论之列,假设就犹如上简单的交互关系就可以了。也就是说想看电影,把光盘放入光驱,光驱开始读盘,就可以看电影了。
现在要求使用程序把这个过程描述出来,该如何具体实现呢?
# 二:解决方案
用来解决上述问题的一个合理的解决方案就是中介者模式。
中介者模式:用一个中介对象来封装一系列的对象交互。中介者使得各对象不需要显式地相互引用。从而使得其耦合松散,而且可以独立的改变它们之间的交互。
# 2.1 解决思路
仔细分析上面的问题,根本原因就在于多个对象需要相互交互,从而导致对象之间紧密耦合,这就不利于对象的修改和维护。
中介者模式的解决思路很简单,跟电脑的例子一样,中介者模式通过引入一个中介对象,让其它的对象都只和中介对象交互,而中介对象知道如何和其它所有的对象交互,这样对象之间的交互关系就没有了,从而实现对象之间的解耦。
对于中介对象而言,所有相互交互的对象,被视为同事类,中介对象就是来维护各个同事之间的关系,而所有的同事类都只是和中介对象交互。
每个同事对象,当自己发生变化的时候,不需要知道这会引起其它对象有什么变化,它只需要通知中介者就可以了,然后由中介者去与其它对象交互。这样松散耦合带来的好处是,除了让同事对象之间相互没有关联外,还有利于功能的修改和扩展。
有了中介者过后,所有的交互都封装到中介者对象里面,各个对象就不再需要维护这些关系了。扩展关系的时候也只需要扩展或修改中介者对象就可以了。
# 2.2 模式结构和说明
中介者模式的结构如图所示:
Mediator:中介者接口。在里面定义各个同事之间交互需要的方法,可以是公共的通讯方法,比如changed方法,大家都用,也可以是小范围的交互方法。
ConcreteMediator:具体中介者实现对象。它需要了解并维护各个同事对象,并负责具体的协调各同事对象的交互关系。
Colleague:同事类的定义,通常实现成为抽象类,主要负责约束同事对象的类型,并实现一些具体同事类之间的公共功能,比如:每个具体同事类都应该知道中介者对象,也就是具体同事类都会持有中介者对象,就可以定义到这个类里面。
ConcreteColleague:具体的同事类,实现自己的业务,在需要与其它同事通讯的时候,就与持有的中介者通信,中介者会负责与其它的同事交互。
# 2.3 示例代码
所有同事的父类定义:
按照前面的描述,所有需要交互的对象,都被视为同事类,这些同事类应该有一个统一的约束。而且所有的同事类都需要和中介者对象交互,换句话说就是所有的同事都应该持有中介者对象。
因此,为了统一约束众多的同事类,并为同事类提供持有中介者对象的公共功能,先来定义一个抽象的同事类,在里面实现持有中介者对象的公共功能。
要提醒一点,下面示例的这个抽象类是没有定义抽象方法的,主要是用来约束所有同事类的类型
/**
* 同事类的抽象父类
*/
public abstract class Colleague {
/**
* 持有中介者对象,每一个同事类都知道它的中介者对象
*/
private Mediator mediator;
/**
* 构造方法,传入中介者对象
*
* @param mediator 中介者对象
*/
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
/**
* 获取当前同事类对应的中介者对象
*
* @return 对应的中介者对象
*/
public Mediator getMediator() {
return mediator;
}
}
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
具体的同事类:(ConcreteColleagueA.java 和 ConcreteColleagueB.java)
/**
* 具体的同事类A
*/
public class ConcreteColleagueA extends Colleague {
public ConcreteColleagueA(Mediator mediator) {
super(mediator);
}
/**
* 示意方法,执行某些业务功能
*/
public void someOperation() {
//在需要跟其它同事通信的时候,通知中介者对象
getMediator().changed(this);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 具体的同事类B
*/
public class ConcreteColleagueB extends Colleague {
public ConcreteColleagueB(Mediator mediator) {
super(mediator);
}
/**
* 示意方法,执行某些业务功能
*/
public void someOperation() {
//在需要跟其它同事通信的时候,通知中介者对象
getMediator().changed(this);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
中介者:
/**
* 中介者,定义各个同事对象通信的接口
*/
public interface Mediator {
/**
* 同事对象在自身改变的时候来通知中介者的方法,
* 让中介者去负责相应的与其它同事对象的交互
*
* @param colleague 同事对象自身,好让中介者对象通过对象实例去获取同事对象的状态
*/
void changed(Colleague colleague);
}
2
3
4
5
6
7
8
9
10
11
12
13
具体中介者:
/**
* 具体的中介者实现
*/
public class ConcreteMediator implements Mediator {
/**
* 持有并维护同事A
*/
private ConcreteColleagueA colleagueA;
/**
* 持有并维护同事B
*/
private ConcreteColleagueB colleagueB;
/**
* 设置中介者需要了解并维护的同事A对象
*
* @param colleague 同事A对象
*/
public void setConcreteColleagueA(ConcreteColleagueA colleague) {
colleagueA = colleague;
}
/**
* 设置中介者需要了解并维护的同事B对象
*
* @param colleague 同事B对象
*/
public void setConcreteColleagueB(ConcreteColleagueB colleague) {
colleagueB = colleague;
}
public void changed(Colleague colleague) {
//某个同事类发生了变化,通常需要与其它同事交互
//具体协调相应的同事对象来实现协作行为
}
}
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
# 2.4 实现示例
要使用中介者模式来实现示例,那就要区分出同事对象和中介者对象。很明显,主板是作为中介者,而光驱、CPU、声卡、显卡等配件,都是作为同事对象。
此时的程序结构如图所示:
先来看看所有同事的抽象父类的定义,跟标准的实现是差不多的:
public abstract class Colleague {
private Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
public Mediator getMediator() {
return mediator;
}
}
2
3
4
5
6
7
8
9
10
11
12
定义众多的同事:
/**
* 光驱类,一个同事类
*/
public class CDDriver extends Colleague {
public CDDriver(Mediator mediator) {
super(mediator);
}
/**
* 光驱读取出来的数据
*/
private String data = "";
/**
* 获取光驱读取出来的数据
*
* @return 光驱读取出来的数据
*/
public String getData() {
return this.data;
}
/**
* 读取光盘
*/
public void readCD() {
//逗号前是视频显示的数据,逗号后是声音
this.data = "设计模式,值得好好研究";
//通知主板,自己的状态发生了改变
this.getMediator().changed(this);
}
}
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
/**
* CPU类,一个同事类
*/
public class CPU extends Colleague {
public CPU(Mediator mediator) {
super(mediator);
}
/**
* 分解出来的视频数据
*/
private String videoData = "";
/**
* 分解出来的声音数据
*/
private String soundData = "";
/**
* 获取分解出来的视频数据
*
* @return 分解出来的视频数据
*/
public String getVideoData() {
return videoData;
}
/**
* 获取分解出来的声音数据
*
* @return 分解出来的声音数据
*/
public String getSoundData() {
return soundData;
}
/**
* 处理数据,把数据分成音频和视频的数据
*
* @param data 被处理的数据
*/
public void executeData(String data) {
// 把数据分解开,前面的是视频数据,后面的是音频数据
String[] ss = data.split(",");
this.videoData = ss[0];
this.soundData = ss[1];
// 通知主板,CPU的工作完成
this.getMediator().changed(this);
}
}
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
/**
* 显卡类,一个同事类
*/
public class VideoCard extends Colleague {
public VideoCard(Mediator mediator) {
super(mediator);
}
/**
* 显示视频数据
*
* @param data 被显示的数据
*/
public void showData(String data) {
System.out.println("您正观看的是:" + data);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 声卡类,一个同事类
*/
public class SoundCard extends Colleague {
public SoundCard(Mediator mediator) {
super(mediator);
}
/**
* 按照声频数据发出声音
*
* @param data 发出声音的数据
*/
public void soundData(String data) {
System.out.println("画外音:" + data);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
定义中介者接口:
由于所有的同事对象都要和中介者交互,来定义出中介者的接口,功能不多,提供一个让同事对象在自身改变的时候来通知中介者的方法
/**
* 中介者对象的接口
*/
public interface Mediator {
/**
* 同事对象在自身改变的时候来通知中介者的方法,
* 让中介者去负责相应的与其它同事对象的交互
*
* @param colleague 同事对象自身,好让中介者对象通过对象实例
* 去获取同事对象的状态
*/
void changed(Colleague colleague);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
实现中介者对象:
/**
* 主板类,实现中介者接口
*/
public class MotherBoard implements Mediator {
/**
* 需要知道要交互的同事类——光驱类
*/
private CDDriver cdDriver = null;
/**
* 需要知道要交互的同事类——CPU类
*/
private CPU cpu = null;
/**
* 需要知道要交互的同事类——显卡类
*/
private VideoCard videoCard = null;
/**
* 需要知道要交互的同事类——声卡类
*/
private SoundCard soundCard = null;
public void setCdDriver(CDDriver cdDriver) {
this.cdDriver = cdDriver;
}
public void setCpu(CPU cpu) {
this.cpu = cpu;
}
public void setVideoCard(VideoCard videoCard) {
this.videoCard = videoCard;
}
public void setSoundCard(SoundCard soundCard) {
this.soundCard = soundCard;
}
public void changed(Colleague colleague) {
if (colleague == cdDriver) {
//表示光驱读取数据了
this.opeCDDriverReadData((CDDriver) colleague);
} else if (colleague == cpu) {
//表示CPU处理完了
this.opeCPU((CPU) colleague);
}
}
/**
* 处理光驱读取数据过后与其它对象的交互
*
* @param cd 光驱同事对象
*/
private void opeCDDriverReadData(CDDriver cd) {
//1:先获取光驱读取的数据
String data = cd.getData();
//2:把这些数据传递给CPU进行处理
this.cpu.executeData(data);
}
/**
* 处理CPU处理完数据后与其它对象的交互
*
* @param cpu CPU同事类
*/
private void opeCPU(CPU cpu) {
//1:先获取CPU处理过后的数据
String videoData = cpu.getVideoData();
String soundData = cpu.getSoundData();
//2:把这些数据传递给显卡和声卡展示出来
this.videoCard.showData(videoData);
this.soundCard.soundData(soundData);
}
}
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
客户端:
public class Client {
public static void main(String[] args) {
// 1:创建中介者——主板对象
MotherBoard mediator = new MotherBoard();
// 2:创建同事类
CDDriver cd = new CDDriver(mediator);
CPU cpu = new CPU(mediator);
VideoCard vc = new VideoCard(mediator);
SoundCard sc = new SoundCard(mediator);
// 3:让中介者知道所有的同事
mediator.setCdDriver(cd);
mediator.setCpu(cpu);
mediator.setVideoCard(vc);
mediator.setSoundCard(sc);
// 4:开始看电影,把光盘放入光驱,光驱开始读盘
cd.readCD();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
运行结果:
如同上面的示例,对于光驱对象、CPU对象、显卡对象和声卡对象,需要相互交互,虽然只是简单演示,但是也能看出来,它们的交互是比较麻烦的,于是定义一个中介者对象——主板对象,来维护它们之间的交互关系,从而使得这些对象松散耦合。
如果这个时候需要修改它们的交互关系,直接到中介者里面修改就好了,也就是说它们的关系已经独立封装到中介者对象里面了,可以独立的改变它们之间的交互关系,而不用去修改这些同事对象。
# 三:模式讲解
# 3.1 认识中介者模式
1. 模式的功能
中介者的功能非常简单,就是封装对象之间的交互。如果一个对象的操作会引起其它相关对象的变化,或者是某个操作需要引起其它对象的后续或连带操作,而这个对象又不希望自己来处理这些关系,那么就可以找中介者,把所有的麻烦扔给它,只在需要的时候通知中介者,其它的就让中介者去处理就可以了。
反过来,其它的对象在操作的时候,可能会引起这个对象的变化,也可以这么做。最后对象之间就完全分离了,谁都不直接跟其它对象交互,那么相互的关系,全部被集中到中介者对象里面了,所有的对象就只是跟中介者对象进行通信,相互之间不再有联系。
把所有对象之间的交互都封装在中介者当中,无形中还得到另外一个好处,就是能够集中的控制这些对象的交互关系,这样有什么变化的时候,修改起来就很方便。
2. 需要Mediator接口吗
要回答这个问题,先要搞清楚一件事情,接口用来干什么的?对,接口是用来实现“封装隔离”的,那么封装谁?隔离谁呢?Mediator接口嘛,肯定是用来封装中介者对象的,使得使用中介者对象的客户对象跟具体的中介者实现对象分离开。
了解了上面这些内容,回过来想想,有没有使用Mediator接口的必要,那就取决于是否会提供多个不同的中介者实现。如果中介者实现只有一个的话,而且预计中也没有需要扩展的要求,那么就可以不定义Mediator接口,让各个同事对象直接使用中介者实现对象;如果中介者实现不只一个,或者预计中有扩展的要求,那么就需要定义Mediator接口,让各个同事对象来面向中介者接口编程,而无需关心具体的中介者实现。
3. 同事关系
在标准的中介者模式中,把使用中介者对象来交互的那些对象称为同事类,这不是乱叫的,在中介者模式中,要求这些类都要继承相同的类,也就是说,这些对象从某个角度讲是同一个类型,算是兄弟对象。
正是这些兄弟对象之间的交互关系很复杂,才产生了把这些交互关系分离出去,单独做成中介者对象,这样一来,这些兄弟对象就成了中介者对象眼里的同事。
4. 同事和中介者的关系
在中介者模式中,当一个同事对象发生了改变,需要主动通知中介者,让中介者去处理与其它同事对象相关的交互。
这就导致了同事对象和中介者对象之间必须有关系,首先是同事对象需要知道中介者对象是谁;反过来,中介者对象也需要知道相关的同事对象,这样它才能与同事对象进行交互。也就是说中介者对象和同事对象之间是相互依赖的。
5. 如何实现同事和中介者的通信
一个同事对象发生了改变,会通知中介者对象,中介者对象会处理与其它同事的交互,这就产生了同事对象和中介者对象的相互通信。怎么实现这种通信关系呢?
一种实现方式是在Mediator接口中定义一个特殊的通知接口,作为一个通用的方法,让各个同事类来调用这个方法,在中介者模式结构图里画的就是这种方式。在前面示例的也是这种方式,定义了一个通用的changed方法,并且把同事对象当做参数传入,这样在中介者对象里面,就可以去获取这个同事对象的实例的数据了。
另外一种实现方式是可以采用观察者模式,把Mediator实现成为观察者,而各个同事类实现成为Subject,这样同事类发生了改变,会通知Mediator。Mediator在接到通知过后,会与相应的同事对象进行交互。
# 3.2 中介者模式的优缺点
1. 松散耦合
中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互不依赖。这样一来,同事对象就可以独立的变化和复用,而不再像以前那样“牵一发而动全身”了。
2. 集中控制交互
多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那就扩展中介者对象,而各个同事类不需要做修改。
3. 多对多变成一对多
没有使用中介者模式的时候,同事对象之间的关系通常是多对多的,引入中介者对象过后,中介者对象和同事对象的关系通常变成了双向的一对多,这会让对象的关系更容易理解和实现。
4. 过度集中化
中介者模式的一个潜在缺点是,如果同事对象的交互非常多,而且比较复杂,当这些复杂性全部集中到中介者的时候,会导致中介者对象变得十分的复杂,而且难于管理和维护。
# 3.3 思考中介者模式
1. 中介者模式的本质
中介者模式的本质:封装交互
中介者模式的目的,就是用来封装多个对象的交互,这些交互的处理多在中介者对象里面实现,因此中介对象的复杂程度,就取决于它封装的交互有多复杂了。
只要是实现封装对象之间的交互功能,就可以应用上中介者模式,而不必过于拘泥于中介者模式本身的结构。标准的中介者模式限制很多,导致能完全按照标准使用中介者模式的地方并不是很多,而且多集中在界面实现上。只要本质不变,稍稍变形一下,简化一下,或许能更好的使用中介者模式。
2. 何时选用中介者模式
建议在如下情况中,选用中介者模式:
如果一组对象之间的通信方式比较复杂,导致相互依赖、结构混乱,可以采用中介者模式,把这些对象相互的交互管理起来,各个对象都只需要和中介者交互,从而使得各个对象松散耦合,结构也更清晰易懂。
如果一个对象引用很多的对象,并直接跟这些对象交互,导致难以复用该对象。可以采用中介者模式,把这个对象跟其它对象的交互封装到中介者对象里面,这个对象就只需要和中介者对象交互就可以了。
# 3.4 相关模式
1. 中介者模式和外观模式
这两个模式有相似的地方,也存在很大的不同。
外观模式多用来封装一个子系统内部的多个模块,目的是向子系统外部提供简单易用的接口,也就是说外观模式封装的是子系统外部和子系统内部模块间的交互;而中介者模式是提供多个平等的同事对象之间交互关系的封装,一般是用在内部实现上。
另外,外观模式是实现单向的交互,是从子系统外部来调用子系统内部,不会反着来,而中介者模式实现的是内部多个模块间多向的交互。
2. 中介者模式和观察者模式
这两个模式可以组合使用。
中介者模式可以组合使用观察者模式,来实现当同事对象发生改变的时候,通知中介对象,让中介对象去进行与其它相关对象的交互。
# 四:JDK
- java.util.Timer (opens new window)
- java.util.concurrent.Executor#execute() (opens new window)
- submit() and invokeXXX() methods of java.util.concurrent.ExecutorService (opens new window)
- scheduleXXX() methods of java.util.concurrent.ScheduledExecutorService (opens new window)
- java.lang.reflect.Method#invoke() (opens new window)