通常,面向对象的软件开发要求尽可能细致地分配责任,从而使每个对象都能够独立完成自己的任务。Observer模式通过尽可能缩小一个对象应对其他对象承担的责任来支持这种责任分配。而Singleton模式将责任集中于其他对象都可以访问和利用的某个特定对象中。与Singleton模式类似的是,Mediator模式也对责任进行了集中,不过这种模式只是对某个特定对象集合的责任进行集中,而不是对整个系统的其他所有对象的责任进行集中。
如果对象群组中的交互错综复杂,而每个对象都需要了解本集合中其他每个对象的情况,那么这个时候采用一个核心机构来负责控制它们之间的交互将非常有用。当这些相关对象之间的交互独立于对象的其他行为的时候,对责任进行集中也很有用。
Mediator模式模式的意图是定义一个对象,该对象将对象集合之间的交互封装起来。利用该模式可以降低对象之间的耦合程序,避免对象之间的显式引用,还可以让对象的交互独立变化。
1. 经典范例:GUI的Mediator模式
当开发GUI应用程序的时候,我们经常会遇到Mediator模式。这些应用程序会慢慢地变得非常庞大,它将原本可以分散到多个类中的代码集中到一起。第4章中给出的ShowFlight类最初就承担三项任务艰巨。在重构之前,这个类作为一个显示面板,是一个完整的GUI应用程序,其轨迹的计算也由它完成。重构之后,飞行轨迹绘制面板类得到了简化,仅包含少量的代码。然而,对于大型应用程序而言,当我们对之进行重构之后,尽管它只包含用于创建组件和管理组件交互的代码逻辑,但是它仍然非常复杂。请参考下面的情况示例。
Oozinoz公司使用橡胶材料箱存放化学品。Oozinoz公司的机器可以读取材料箱的条码,从而跟踪该材料箱在工厂中的当前位置。但有时候,特别是要人工移动材料箱而不是等待机器人移动的时候,就要夺取机器的控制权,进行手工操作。
在app.mediator.moveTub包的MoveATub应用程序中,当用户从左侧的列表框中选定一台机器时,材料箱列表框中的选项将显示当前那台机器的材料箱列表。用户可以选定其中的一个材料箱,然后选定一台目标机器,最后单击“Do it!“按钮,即可更新该材料箱的位置。下图给出了该应用程序的某些类图。
MoveATub类包含了组件构建、事件处理以及数据库模拟的方法
处理该应用程序的开发人员最初用向导进行创建,并已开始重新构造。MoveATub类中的大约半数方法滞后初始化那些包括应用程序GUI组件的变量。assignButton()方法就是一个典型的例子:
private JButton assignButton()
{
if(assignButton == null)
{
assignButton = new JButton("Do it!");
assignButton.setEnabled(false);
assignButton.addActionListener(this);
}
return assignButton;
}
程序员已经删除那些由向导自动生成的用来指定按钮位置和大小的硬编码数字。但现在的问题是MoveATub类将很多功能混合在一起。
大多数静态方法都提供包含材料箱名和机器名的模拟数据库。开发者最终决定放弃仅仅使用名称的方法,并且升级应用程序以使用Tub对象和Machine对象。应用程序中保留下来的大部分方法都包含处理应用程序事件的代码逻辑。例如,valueChanged()方法控制分配按钮是否可用:
我们可能会想到将valueChanged()方法和其他的事件处理方法移植到一个独立的中介者类中。但首先应该注意到,Mediator模式已经应用到这个类中:组件并不能直接更新另外一个组件。例如,不论是机器还是列表框组件都不能直接更新分配按钮。MoveATub应用程序注册了列表框的选择事件,并且基于两个列表框的选项是否被选中来更新按钮。在材料箱移动应用程序中,MoveATub对象作为中介者负责接收事件,以及分派相应的处理动作。
Java类库的结构使你可以使用Mediator模式,虽然JCL并不要求应用程序必须是其自身的中介者。为了避免将组件创建方法、事件处理方法与数据库模拟方法混杂在一个类中,我们可以将所有事件处理方法移入一个单独的中介者类中。
突破题:对MoveATub进行重构,引入一个单独的数据库模拟类,以及用来接收MoveATub的GUI事件的中介者类。请完成以下类图:
public void valueChanged(ListSelectionEvent e)
{
//...
gui.assignButton().setEnabled(!gui.tubList.isSelectedEmpty()
&& !gui.machineList().isSelectionEmpty());
}
有些开发者不喜欢这些特性,但是你希望使用一个类来负责GUI组件构造和布局,另外一个类负责组件交互和用例流。
经过重构,我们将中介者作为一个单独的类,以便对之进行单独开发和集中处理。这样,当MoveATub应用程序运行的时候,组件便会把事件传递给MoveATubMediator对象。该中介者可能对非GUI对象采取动作:比如,当做出材料箱分配时更新数据库。该中介者也可能会回调GUI中的组件对象:例如,在分配操作完成后禁用该按钮。
突破题:请绘制出当图形用户单击“Do it!“按钮后所发生的事情,说明你认为哪个对象最重要,以及这些对象之间传递的消息流。
本图凸显中介者的核心角色
这个解决方案凸显中介者作为分发者的重要作用,即接收某事件,并采取职责来更新其影响的所有对象。
GUI组件可能会理所当然地使用Mediator模式,在事件产生时只通知中介者,而不直接更新其他组件。GUI应用程序可能是最常使用Mediator模式的应用,但在其他情况下,我们同样也可以使用Mediator模式。
每当对象集合内的交互行为错综复杂的时候,我们就可以在该组对象之外创建一个中介者类来集中处理这些行为。这样,对象集合中每个对象不必为其他对象负责,从而降低集合中对象之间的耦合程序,也就是松散耦合。在一个独立的对象中集中管理对象交互行为还会带来另外一个好处,那就是简化和标准化对象间的交互规则。例如,我们可以利用中介者模式来管理关系的完整性。
解答:
MoveATub类负责构建组件,而MoveATubMediator类负责处理事件,将该应用程序的组件创建、事件处
理以及数据库模拟等功能相分离
在这种设计中,该中介者类拥有Flower et al.[1999]称作的“令人羡慕的特性”,在GUI类中,这个类看起来比自身更加有趣,valueChanged()方法如下: