思维导图
1、场景问题
大家都知道电脑的主要配件有:CPU、内存、硬盘、显卡、声卡、网卡、光驱、主板等,这些配件它们之间都是通过主板来完成相互之间的交互工作,但是如果没有了主板会怎么样呢?
如果没有了主板情况,那么各个配件之间就需要自行相互交互,以相互传送数据,如下图:
如果有主板的情况,各个配件的交互完全通过主板来完成,每个配件都只需要和主板交互,而主板知道如何和所有的配件交互,如下图:
如果上面的情况发生在软件开发中呢? 就相当于出现了多个类之间相互交互,而且交互的很频繁,导致每个类都必须知道所有需要交互的类,也就是我们常说的类和类耦合了,是不是很麻烦?那该如何来简化这种多个对象之间的交互呢? ----> 使用中介者模式来解决。
演示案例:程序模拟用电脑来看电影,简化后的流程如下:
第一步:首先是光驱要读取光盘上的数据,然后告诉主板,它的状态改变了
第二步:主板去得到光驱的数据,把这些数据交给CPU进行分析处理
第三步:CPU处理完后,把数据分成了视频数据和音频数据,通知主板,它处理完了
第四步:主板去得到CPU处理过后的数据,分别把数据交给显卡和声卡,去显示出视频和发出声音
上面的流程是持续的、不断重复的直到电影播放完毕,下面使用程序把这个过程实现出来:
2、解决方案 --- 使用中介者模式来解决问题
2.1 中介者模式的定义:
用一个中介对象来封装一系列的对象交互。中介者使得各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
2.2 使用中介者模式来解决问题的思路:
通过分析上面的问题,根本原因就在于多个对象需要相互交互,从而导致对象之间紧密耦合,不利于对象的修改和维护。
使用中介者模式解决问题的思路很简单,中介者模式通过引入一个中介者对象,让其他的对象都只和中介对象交互,而中介对象知道如何和其他所有的对象交互,这样对象之间的依赖关系就没有了,从而实现了对象之间的解耦。(对于中介对象而言,所有相互交互的对象,被视为同事类;中介对象就是来维护各个同事之间的关系,而所有的同事类都只是和中介对象交互。)
2.3 中介者模式的结构和说明:
说明:
Mediator: 中介者接口。在里面定义各个同事之间交互需要的方法。
ConcreteMediator: 具体中介者实现对象。它需要了解并维护各个同事对象,并肩负具体的协调各同事对象的交互关系。
Colleague: 同事类的定义,通常实现成为抽象方法,主要负责约束同事对象的类型,并实现一些具体同事类之间的公共功能。
ConcreteColleague: 具体的同事类,实现自己的业务,在需要与其他同事通信的时候,就与持有的中介者通信,中介者会负责与其他的同事交互。
中介者模式的示例代码:
1 /**
2 * 同事类的抽象父类 5 */
6 public abstract class Colleague {
7
8 //持有中介者对象,每一个同事类都知道它的中介者对象
9 private Mediator mediator;
10
11 /**
12 * 构造方法,传入中介者对象
13 * @param mediator
14 */
15 public Colleague(Mediator mediator) {
16 this.mediator = mediator;
17 }
18
19 /**
20 * 获取当前同事类对应的中介者对象
21 * @return
22 */
23 public Mediator getMediator() {
24 return mediator;
25 }
26 }
27
28 /**
29 * 具体的同事类A
30 */
31 public class ConcreteColleagueA extends Colleague {
32
33 public ConcreteColleagueA(Mediator mediator) {
34 super(mediator);
35 }
36
37 /**
38 * 示意方法,执行某些业务功能
39 */
40 public void someOperation(){
41 //在需要跟其他同事通信的时候,通知中介者对象
42 getMediator().change(this);
43 }
44 }
45
46 /**
47 * 具体的同事类B
48 */
49 public class ConcreteColleagueB extends Colleague {
50
51 public ConcreteColleagueB(Mediator mediator) {
52 super(mediator);
53 }
54
55 /**
56 * 示意方法,执行某些业务功能
57 */
58 public void someOperation(){
59 //在需要跟其他同事通信的时候,通知中介者对象
60 getMediator().change(this);
61 }
62 }
63
64
65 /**
66 * 中介者,定义各个同事对象通信的接口
67 */
68 public interface Mediator {
69 /**
70 * 同事对象在自身改变的时候来通知中介者的方法
71 * 让中介者去负责相应的与其他同事对象的交互
72 * @param colleague 同事对象自身
73 */
74 public void change(Colleague colleague);
75 }
76
77 /**
78 * 具体的中介者实现 81 */
82 public class ConcreteMediator implements Mediator {
83
84 //持有并维护同事A
85 private ConcreteColleagueA colleagueA;
86
87 //持有并维护同事B
88 private ConcreteColleagueB colleagueB;
89
90 public ConcreteMediator() {
91 }
92
93 @Override
94 public void change(Colleague colleague) {
95 //某个同事类发生了变化,通常需要与其他同事交互
96 //具体协调相应的同事对象来实现协作行为
97 }
98
99 /**
100 * 设置中介者需要了解并维护的同事A对象
101 * @param colleagueA 同事A对象
102 */
103 public void setColleagueA(ConcreteColleagueA colleagueA) {
104 this.colleagueA = colleagueA;
105 }
106
107 /**
108 * 设置中介者需要了解并维护的同事B对象
109 * @param colleagueB 同事B对象
110 */
111 public void setColleagueB(ConcreteColleagueB colleagueB) {
112 this.colleagueB = colleagueB;
113 }
114 }
使用中介模式来描述电脑看电影的流程:
1 /**
2 * 所有同事的抽象父类
3 */
4 public abstract class Colleague {
5 private Mediator mediator;
6
7 public Colleague(Mediator mediator){
8 this.mediator = mediator;
9 }
10
11 public Mediator getMediator() {
12 return mediator;
13 }
14 }
15
16 /**
17 * 同事类: 光驱类
18 */
19 public class CDDriver extends Colleague {
20 //光驱读取出来的数据
21 private String data = "";
22
23 public CDDriver(Mediator mediator) {
24 super(mediator);
25 }
26
27 /**
28 * 读取光盘
29 */
30 public void readCD(){
31 this.data = "学习研磨设计模式,中介者模式";
32 //通知主板,自己的状态发生了改变
33 this.getMediator().changed(this);
34 }
35
36 public String getData() {
37 return data;
38 }
39 }
40
41 /**
42 * 同事类: CPU
43 */
44 public class CPU extends Colleague{
45 //分析出来的视频数据
46 private String videoData = "";
47 //分析出来的音频数据
48 private String soundData = "";
49
50 public CPU(Mediator mediator) {
51 super(mediator);
52 }
53
54 /**
55 * 处理数据,把数据 分成音频和视频的数据
56 * @param data
57 */
58 public void executeData(String data){
59 //把数据分解开,前面的是视频数据,后面的是音频数据
60 String[] ss = data.split(",");
61 this.videoData = ss[0];
62 this.soundData = ss[1];
63
64 //通知主板,CPU的工作完成了
65 this.getMediator().changed(this);
66 }
67
68 public String getVideoData() {
69 return videoData;
70 }
71
72 public String getSoundData() {
73 return soundData;
74 }
75 }
76
77 /**
78 * 同事类 : 显卡类
79 */
80 public class VideoCard extends Colleague {
81
82 public VideoCard(Mediator mediator) {
83 super(mediator);
84 }
85
86 /**
87 * 显示视频数据
88 * @param data 被显示的数据
89 */
90 public void showData(String data){
91 System.out.println("您正观看的是 : " + data);
92 }
93 }
94
95 /**
96 * 同事类: 声卡类
97 */
98 public class SoundCard extends Colleague {
99
100 public SoundCard(Mediator mediator) {
101 super(mediator);
102 }
103
104 /**
105 * 按照声频数据发出声音
106 */
107 public void soundData(String data){
108 System.out.println("画外音: " + data);
109 }
110 }
111
112 /**
113 * 中介者对象的接口
114 */
115 public interface Mediator {
116 /**
117 * 同事对象在自身改变的时候来通知中介者的方法,
118 * 让中介者去负责相应的与其他同事对象的交互
119 * @param colleague 同事对象自身,好让中介者对象通过对象实例去获取同事对象的状态
120 */
121 public void changed(Colleague colleague);
122 }
123
124 /**
125 * 主板类,实现中介者接口
126 */
127 public class MediatorBoard implements Mediator {
128
129 //需要知道要交互的同事类--光驱类
130 private CDDriver cdDriver = null;
131 //需要知道要交互的同事类--CPU类
132 private CPU cpu = null;
133 //需要知道要交互的同事类--显卡类
134 private VideoCard videoCard = null;
135 //需要知道要交互的同事类--声卡类
136 private SoundCard soundCard = null;
137
138 @Override
139 public void changed(Colleague colleague) {
140 if(colleague == cdDriver){
141 //表示光驱读取数据了
142 this.openCDDriverReadData((CDDriver)colleague);
143 }else if(colleague == cpu){
144 //表示CPU处理完了
145 this.openCPU((CPU)colleague);
146 }
147 }
148
149 private void openCDDriverReadData(CDDriver cdDriver){
150 //1. 先获取光驱读取的数据
151 String data = cdDriver.getData();
152 //2. 把这些数据传递给CPU进行处理
153 this.cpu.executeData(data);
154 }
155
156 /**
157 * 处理CPU处理完数据后与其他对象的交互
158 */
159 private void openCPU(CPU cpu){
160 //1. 先获取CPU处理后的数据
161 String videoData = cpu.getVideoData();
162 String soundData = cpu.getSoundData();
163 //2. 把这些数据传递给显卡和声卡
164 this.videoCard.showData(videoData);
165 this.soundCard.soundData(soundData);
166 }
167
168 public void setCdDriver(CDDriver cdDriver) {
169 this.cdDriver = cdDriver;
170 }
171
172 public void setCpu(CPU cpu) {
173 this.cpu = cpu;
174 }
175
176 public void setVideoCard(VideoCard videoCard) {
177 this.videoCard = videoCard;
178 }
179
180 public void setSoundCard(SoundCard soundCard) {
181 this.soundCard = soundCard;
182 }
183 }
184
185 /*
186 测试类
187 */
188 public class Client {
189 public static void main(String[] args) {
190 //1.创建中介者---主板对象
191 MediatorBoard mediator = new MediatorBoard();
192
193 //创建同事类
194 CDDriver cd = new CDDriver(mediator);
195 CPU cpu = new CPU(mediator);
196 VideoCard vc = new VideoCard(mediator);
197 SoundCard sc = new SoundCard(mediator);
198
199 //让中介者知道所有的同事
200 mediator.setCdDriver(cd);
201 mediator.setCpu(cpu);
202 mediator.setVideoCard(vc);
203 mediator.setSoundCard(sc);
204
205 //开始看电影
206 cd.readCD();
207 }
208 }
3、模式讲解
3.1中介者模式的功能
中介者模式的功能非常简单,就是封装对象之间的交互。把所有对象之间的交互封装在中介者当中,无形中还可以得到另外一个好处,就是能够集中地控制这些对象的交互关系,这样当有变化的时候,修改起来就很方便。
3.2 需要Mediator接口吗?
首先要明白接口是用来实现"封装隔离的",那么封装谁?隔离谁呢? Mediator接口就是用来封装中介者对象的,使得使用中介者对象的同事对象跟具体的中介者实现分离开。那么有没有使用Mediator接口的必要,那就取决于是否会提供多个不同的中介者实现,如果中介者实现只有一个的话,而且预计中也没有需要扩展的要求,那么就可以不定义Mediator接口,让各个同事类直接使用中介者实现对象。
3.3 中介者模式的调用顺序示意图
4、广义中介者
查看上面标准的中介者模式的结构、定义和示例后,会发现几个问题,使得中介者模式在实际使用的时候,变得繁琐和困难:
问题一:是否有必要要同事对象定义一个公共的父类?
Java是单继承的,如果为了使用中介者模式,就让这些同事对象继承了一个父类,这是很不好的。
问题二:同事类有必要持有中介者对象吗?
同事类需要知道中介者对象,以便当它们发生改变的时候能够通知中介者对象。但是是否需要作为属性并通过构造方法传入这么强的依赖关系呢?
问题三:是否需要中介者接口?
在实际开发中,很常见的情况是不需要中介者接口的,中介者通常实现成单列。
问题四:中介者对象是否需要持有所有的同事?
问题五:中介者对象只是提供一个公共的方法来接受同事对象的通知吗?
在示例的公共方法里,需要去区分到底是谁调的。在实际开发中,通常会提供具体的业务通知方法,这样就不用再去判断到底是什么对象,具体是什么业务了.
基于上面的考虑,在实际应用开发中,经常会简化中介者模式,来使开发变得简单,比如有如下的简化:
1.通常会去掉同事对象的父类,这样就可以让任意的对象,只要需要相互交互,就可以成为同事。
2.通常不定义Mediator接口,把具体的中介者对象实现成为单列
3.同事对象不再持久中介者,而是在需要的时候直接获取中介者对象并调用
4.中介者也不再持有同事对象,而是在具体处理方法里面去创建,或者获取,或者从参数传入需要的同事对象
经过上面4个步骤的简化、变形使用的情况称为广义中介者。
5、思考中介者模式
5.1 中介者模式的优缺点
优点:
1.松散耦合。 中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互不依赖。
2.集中控制交互。 多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了。
3.多对多变成一对多。没有使用中介者模式的时候,同事对象之间的关系通常是多对多的,引入中介者对象以后,中介者对象和同事对象的关系通常就变成了双向的一对多。
缺点:
中介者模式的一个潜在缺点:过度集中化。如果同事对象的交互非常多,而且比较复杂,当这些复杂性全部集中到中介者的时候,会导致中介者对象变得十分复杂,而且难于管理和维护。
5.2 中介者模式的本质
中介者模式的本质:封装交互。
5.3 何时选用中介者模式
1. 如果一组对象之间的通信方式比较复杂,导致相互依赖、结构混乱,可以采用中介者模式,把这些对象相互的交互管理起来,各个对象都只需要和中介者交互,从而使得各个对象松散耦合,结构也更清晰易懂。
2. 如果一个对象引用很多的对象,并直接跟这些对象交互,导致难以复用该对象,可以采用中介者模式,把这个对象跟其他对象的交互封装到中介者对象里面,这个对象只需要和中介者对象交互就可以了。
----------------------------------------------------
参考:
《研磨设计模式》