模式目的
中介的含义不难理解,以具体的生活场景为例,在租房、留学、找工作等时期或多或少都需要中介的帮助。互联网上实时更新的海量信息与错综复杂的关系让你无法分辨真实性与参考性,所以你无法躲避这些中介关系,只能够通过其获取你所需要的信息。 在软件世界也同样如此,当有多个对象彼此间相互交互的时候,自然就会想到对象间的耦合度过高,而中介者模式就是用来解决这一问题的,通过封装对象间的交互行为,来降低对象之间的耦合性,防止出现系统或模块内部过度耦合。以下的图片展现了具备复杂关联关系的对象模拟图。 图2.1 复杂关联关系模拟图 根据上图以及以往程序设计中的经验,我们可以推断出程序设计中存在过多复杂关联关系带来的问题:
- 系统结构复杂:对象之间存在大量的相互关联和调用,若其中一个对象发生变化,则需要跟踪和该对象关联的其他所有对象并进行相应处理。
- 对象可重用性差:由于一个对象和其他对象具有很强的关联,若不存在其他对象的支持,一个对象很难被另一个系统或模块重用,这些对象表现像一个不可分割的整体,职责较为混乱。
- 系统扩展性低:增加一个新对象需要在原有相关对象上增加引用,同时调整原有对象,系统耦合度高,对象操作很不灵活,扩展性差。
在面向对象的软件设计与开发过程中,根据“单一职责原则”,我们应该尽量将对象细化,使其只负责或呈现单一的职责。对于一个可能由很多对象构成的模块,这些对象之间可能存在相互的引用,为了减少对象两两之间复杂的引用关系,使之成为一个松耦合的系统,我们需要使用中介者模式,下图十分生动地展现了中介者模式的意义。
图2.2 中介者模式应用模拟图 由上述的描述可知,中介者模式的目的主要是为了降低模块内部之间类的相互引用,防止出现系统或模块内部过度耦合。
实现方法
从问题出发,我们总结出中介者需要承担两方面的职责:
- 中转作用(结构性): 通过中介者提供的中转作用,各个同事对象就不再需要显式引用其他同事,当需要和其他同事进行通信时,通过中介者即可。
- 协调作用(行为性): 中介者可以更进一步的对同事之间的关系进行封装,同事可以一致地和中介者进行交互,而不需要指明中介者需要具体怎么做,中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装。
从中介者模式的组成而言,其主要包含四个角色:
- Mediator(抽象中介者):定义一个接口用于与各同事对象之间的通信
- ConcreteMediator(具体中介者):抽象中介者的子类,通过协调各个同事对象来实现协作行为,了解并维护它的各个同事对象的引用;
- Colleague(抽象同事类):定义各同事的公有方法;
- ConcreteColleague(具体同事类):抽象同事类的子类,每一个同事对象都引用一个中介者对象;每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事类的通信;在具体同事类中实现了在抽象同事类中定义的方法。
下图展示了中介者模式中的类结构,可以清楚地看到四种角色之间的关系。 图3.1 中介者模式类图
中介者模式可以方便地应用于图形界面(GUI)开发中,在比较复杂的界面中可能存在多个界面组件之间的交互关系。对于这些复杂的交互关系,我们可以引入一个中介者类,将这些交互的组件作为具体的同事类,将它们之间的引用和控制关系交由中介者负责,在一定程度上简化系统的交互, MVC架构中控制器(Controller)作为一种中介者,是数据模型对象与视图之间的桥梁,它监听视图上的交互动作并且对视图的状态作出反应,同时在Model通知数据发生改变的时候更新Model的状态。以移动应用开发为例,当用户点击按钮或在View中输入信息后视图会通知控制器,控制器解释用户交互逻辑,然后联系模型获取完成请求所需的信息。模型将信息提供给控制器,控制器将其中继到视图,视图将其显示给用户。
图3.2 MVC模式
模式简例
科技发达之后家里所有设备都是智能化的,而小明在家洗澡的时候有个习惯就是喜欢听歌,而且洗澡时候还要把窗帘拉上。因此就有这种情形,小明拉上窗帘说明洗浴设备需要工作,并且音乐设备也将唤醒。因此我们希望小明家的洗浴设备、音响设备和窗帘设备能协同合作,不管操作哪种设备,其他两种设备都有响应。 现在很明显我们可以看出来,我们有三个对象,也就是三种设备,程序看起来也很简单,只要在启动一个设备时,能同时启动另外两个设备。那么我们只需在一个设备的类里放两个其他设备的引用,很容易就能解决这个问题,这样写出来的程序对象关系如下图。 图4.1 修改前模式 该程序最主要的功能就是在一个类里完成了对另外两个类的方法作用。但是这样设计,由耦合度过高的问题带来两个主要缺陷
- 一旦窗帘设备损坏需要更换,那么就要让新的窗帘设备建立所有其他设备的连接
- 若产生新的的需求,如洗澡的时候要把门锁住,那门锁的智能设备又要关联所有设备,这样的程序设计使得在扩展程序功能时更加麻烦。
中介者模式的引入则极大的弥补了上述程序的两个缺陷,对象交互如下图: 图4.2 修改后模式
中介者就好比智能家居的管家,所有对象的交互指令都通过它来传达。中介者负责与对象联系,对象与对象之间不再进行直接的交互,也就是对对象关系进行解耦。
图4.2 程序类图
public class MusicDevice extends Device{
public void operateDevice(String str) {
if(str != "MusicDevice"){
System.out.println("音乐设备已启动");
}
if(str == "true"){
Mediator();
}
}
public void Mediator(){
ConcreteMediator mediator = new ConcreteMediator();
mediator.startUp("MusicDevice");
}
}
音乐设备部分的代码如上,其他设备代码可以参照此例。设备类都继承自Device类,其中主要由两个方法组成,一个是operateDevice函数,用来启动设备,另一个是Mediator函数,用来调用中介者,并以中介者为媒介启动其他设备。Mediator由operateDevice函数进行调用,并设置了判断条件,防止反复调用中介者类。
public class ConcreteMediator extends SmartMediator{
public void startUp(String item){
MusicDevice musicDevice = new MusicDevice();
CurtainDevice curtainDevice = new CurtainDevice();
BathDevice bathDevice = new BathDevice();
musicDevice.operateDevice(item);
curtainDevice.operateDevice(item);
bathDevice.operateDevice(item);
}
}
中介者类如上,即程序最为重要的部分。当它的startUp方法被某一设备调用之后,它会新建所有设备的类,并且调用除了已启动的设备之外,所有其他设备的启动方法。在启动方法中设置了参数,以防止循环调用。当我们想要添加新的设备的时候,只需要编写新设备类,并且在中介者类中添加该类的启动方法,就能完成修改。从而降低代码之间的耦合性,提高了程序的可维护性。
程序分析:
- 该程序解决了上述两个缺陷,现在所有设备都只与中介者相关联,不管是新增设备还是替换设备,只需要改变设备与中介者之间的关系,降低了代码之间的耦合性。
- 改变设计之后所有设备的启动项都由中介者管理,用户可以通过中介者方便地启动所有设备,而不需要将所有设备之间连接起来。避免了随着设备的增多,设备之间的联系变得无比复杂的情况。
模式总结
-
中介者模式优点:
- 简化了对象之间的交互。
- 将各同事解耦,减少子类生成,增加对象的复用性。
- 简化各同事类的设计和实现,便于系统维护。
-
中介者模式缺点:
- 在具体中介者类中包含了同事之间的交互细节,如果中介者类设计的不好,可能会导致具体中介者类非常复杂,使得系统难以维护。
- 中介者对象本身需要整理系统内部的对象之间的交互,可能会导致其本身会变得臃肿和复杂,进而变得难以维护
-
中介者模式的适用环境:
- 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解。
- 一个对象引用了很多对象并且直接和这些对象通信,导致难以复用该对象。
- 希望通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,在中介者中定义对象。
- 交互的公共行为,如果需要改变行为则可以增加新的中介者类。
参考资料
中介者模式-维基百科 中介者模式— Graphic Design Patterns - 图说设计模式 设计模式 | 中介者模式及典型应用 中介者模式 - jyqdaisy - 博客园