基础知识(08) -- 中介者模式
思维导图
----------------------------------------------------------------------
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. 如果一个对象引用很多的对象,并直接跟这些对象交互,导致难以复用该对象,可以采用中介者模式,把这个对象跟其他对象的交互封装到中介者对象里面,这个对象只需要和中介者对象交互就可以了。
----------------------------------------------------
参考:
《研磨设计模式》