第3章 中介模式

3.1 从生活中领悟中介模式

3.1.1 故事剧情—找房子问中介

人在江湖漂,岂能总是顺心如意?大多数毕业生的第一份工作很难持续两年以上,与他们一样,Tony也在一家公司工作了一年半后,换了一个东家。

在北京这个大城市里,换工作基本就意味着换房子。不得不说,找房子是一件烦心而累人的事情!

● 首先,要清楚自己要怎样的房子:多大面积(多少平方米),什么价位,是否有窗户,是否有独立卫生间。

● 然后,要去网上查找各种房源信息,找到最匹配的几个户型。

● 之后,还要电话咨询,过滤虚假信息和过时信息。

● 接着,是最累人的一步,还要实地考察,看看真实的房子与网上的信息是否相符,房间是否有异味,周围设施是否齐全。这一步你可能会从东城穿越到西城,再来到南城,而后又折腾去北城……想想都累!

● 最后,还要与各种脾性的房东周旋,讨价还价。

Tony 想了想,还是找中介算了。在北京这座城市,你几乎找不到一手房东,因为90%的房源信息都掌握在房屋中介手中!既然都找不到一手房东,还不如找一家正规点的中介。

于是Tony找到了我爱我家,认识了里面的职员Wingle。Wingle问他对房子的要求,Tony说:“18平方米左右,要有独立卫生间,要有窗户,最好朝南,有厨房更好!价位在2000元左右。”Wingle立马就说:“上地西里有一间,但没有厨房;当代城市家园有两间,一间主卧,一间次卧,但卫生间是共用的;金隅美和园有一间,比较适合你,但价格会贵一点。”Wingle对房源真是了如指掌啊!说完就带着我开始看房了……

一天就找到了还算合适的房子。但不得不再次吐槽:北京的房子真的贵得离谱啊,16平方米,精装修,有朝南窗户,一个超小(1平方米宽不到)的阳台,卫生间5人共用,厨房共用,价格是每月2600元。押一付三,加一个月的中介费,一次交了一万多元,Tony要开始吃土了,内心滴了无数滴血……

3.1.2 用程序来模拟生活

在上面的生活场景中,Tony通过中介来找房子,因为找房子的过程实在太烦琐了,而且对房源信息也不了解。通过中介,他省去了很多麻烦的细节,合同也是直接跟中介签的,甚至都不知道房东是谁!

我们通过程序来模拟一下上面找房子的过程。

源码示例3-1 模拟故事剧情

测试代码:

输出结果:

3.2 从剧情中思考中介模式

3.2.1 什么是中介模式

Define an object that encapsulates how a set of objects interact.Mediator promotes loose coupling by keeping objects from referring to each other explicitly,and it lets you vary their interaction independently.

用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

在前面的故事剧情中,由中介来承接房客与房东之间的交互过程,可以使得整个过程更加畅通、高效。这在程序中叫作中介模式,中介模式又称为调停模式。

3.2.2 中介模式设计思想

从故事剧情的示例中我们知道,Tony找房子并不需要与房东进行直接交涉,甚至连房东是谁都不知道,他只需要与中介进行交涉即可,一切都可通过中介完成。这使得他找房子的过程,由如图3-1所示的状态变成了如图3-2所示的状态,这无疑为他减少了不少麻烦。

图3-1 没有中介的找房过程

图3-2 有中介的找房过程

在很多系统中,多个类很容易相互耦合,形成网状结构。中介模式的作用就是将这种网状结构(如图3-3所示)分离成星型结构(如图3-4所示)。这样调整之后,使得对象间的结构更加简洁,交互更加顺畅。

图3-3 网状结构

图3-4 星型结构

3.3 中介模式的模型抽象

3.3.1 代码框架

从模拟故事剧情(源码示例3-1)的代码中,我们可以抽象出中介模式的框架模型。

源码示例3-2 中介模式的框架模型

3.3.2 类图

根据上面的示例代码,我们可以大致地构建出中介模式的类图,如图3-5所示。

Mediator 就是中介类,用来协调对象间的交互,如故事剧情中的 HousingAgency。中介类可以有多个具体实现类,如MediatorImplA和MediatorImplB。InteractiveObject是要进行交互的对象,如故事剧情中的HouseOwner和Customer。InteractiveObject可以是互不相干的多个类的对象,也可以是具有继承关系的相似类。

图3-5 中介模式的类图

3.3.3 模型说明

1.设计要点

中介模式主要有以下三个角色,在设计中介模式时要找到并区分这些角色:

(1)交互对象(InteractiveObject):要进行交互的一系列对象。

(2)中介者(Mediator):负责协调各个对象之间的交互。

(3)具体中介者(Mediator):中介的具体实现。

2.中介模式的优缺点

优点:

(1)Mediator 将原本分布于多个对象间的行为集中在一起,作为一个独立的概念并将其封装在一个对象中,简化了对象之间的交互。

(2)将多个调用者与多个实现者之间多对多的交互关系,转换为一对多的交互关系,一对多的交互关系更易于理解、维护和扩展,大大减少了多个对象之间相互交叉引用的情况。

通过中介找房子给我们带来了很多的便利,但也存在诸多明显问题:

(1)很容易遇到黑中介(比如各种不规范和坑,也许你正深陷其中)。

(2)高昂的中介费(给本就受伤的心灵又多补了一刀)。

中介模式也有很多缺点:

(1)中介者承接了所有的交互逻辑,交互的复杂度转变成了中介者的复杂度,中介者类会变得越来越庞大和复杂,以至于难以维护。

(2)中介者出问题会导致多个使用者同时出问题。

3.4 实战应用

再举一个实际应用中的例子。不管是QQ、钉钉这类支持视频通信的社交软件,还是51Talk、ABC360这类互联网在线教育产品,都需要和通信设备(扬声器、麦克风、摄像头)进行交互。在移动平台,各类通信设备一般只会有一个,但在PC端(尤其是Windows系统下),你可能会有多个扬声器、多个麦克风,甚至多个摄像头,还可能会在通话的过程中由麦克风A切换到麦克风B。

如何与这些繁杂的设备进行交互呢?聪明的你一定会想:用中介模式啊!对,就是它,我们先看一下程序的设计结构,如图3-6所示。

图3-6 设备交互程序的设计结构

图3-6中的DeviceUtil其实就是中介者,客户端界面通过DeviceUtil这个中介者与设备进行交互,这样界面类ClientWidget就不用同时维护三个DeviceMgr的对象,而只要与一个DeviceUtil的对象进行交互就可以了。ClientWidget可通过DeviceUtil枚举各种类型的设备(扬声器、麦克风、摄像头),同时可以通过DeviceUtil读取和保存当前正在使用的各种类型设备。

这时,可能有读者要问了:为什么DeviceUtil到DeviceMgr的依赖指向与模型图不一样呢?这是因为这个应用中,ClientWidget 与 DeviceMgr 是单向交互的,只有 ClientWidget 调用DeviceMgr,而一般不会有DeviceMgr调用ClientWidget的情况。而模型图同时支持双向的交互,InteractiveObject 通过直接依赖与 Mediator 进行交互,而 User 也通过 Mediator 间接地与InteractiveObjectImplA、InteractiveObjectImplB进行交互(如图3-5中虚线所示)。

下面,我们根据设计图来实现代码。

源码示例3-3 设备管理器

测试代码:

输出结果:

3.5 应用场景

(1)一组对象以定义良好但复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。

(2)一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。

(3)想通过一个中间类来封装多个类中的行为,同时又不想生成太多的子类。