设计模式笔记(九)– 中介者模式

​中介者模式可以实现互不依赖的各个类之间进行通信协作。


问题提出

只要创建过桌面应用程序或移动端应用程序,就应该使用过中介者模式,但可能你自己都不知道已在使用该模式。

比如,左侧是文章列表,右侧是编辑文章的表单。“Save”按钮当前是禁用的,因为未选择任何文章。

当我们选择文章1(Article 1)时,文本框将被填充而且我们的”Save”按钮也启用了。

当我们删除文本框的内容时,按钮又被禁用了。

我们在很多应用程序里见过这种模式,当有很多需要互相协作的输入字段时,就会有这种情况。

假设我们是图形框架的开发人员,创建出可重用的TextBox、ListBox等组件给其他开发人员使用。

创建共同的基类UIControl。

创建扩展于UIControl的ListBox类,它有selection属性。

创建扩展于UIControl的TextBox类,它有content属性。

创建扩展于UIControl的Button类,它有isEnabled属性。

这些类属于通用的组件,用户使用这些类时是不能修改其源代码的,而且使用这些组件的场景也是各不相同的。如何让这些类互相通信呢?

中介者模式就是解决这个问题的一个方案。


解决方案

一种方法是继承。

创建ArticleList Box,TitleTextBox,SaveButton分别继承于ListBox,TextBox,Button。

但这种方式有很多连接和依赖。

ArticlesListBox需要依赖TitleTextBox和SaveButton,因为选择某一个Article时,需填充TitleTextBox并启用SaveButton。TitleTextBox要依赖SaveButton,在清除TitleTextBox时要禁用SaveButton。而SaveButton反过来也要依赖TitleTextBox,因为它要从TitleTextBox获取数据再写入到数据库。

当表单组件越来越多时,这种依赖就会很复杂,而且相关控制逻辑遍布每个地方,要搞清楚该表单的逻辑,需要查看每一个不同的类。后期就会越来越难以理解,越来越无法维护了。

我们需要清理这些麻烦,寻找更好的解决方案。

我们对各UIControl继承类作些小更改,引入一个DialogBox类,每一个UIControl都一个DialogBox对象,可以命名为比如owner,我们将该字段提取到基类UIControl里,以免在每个UIControl继承类里重复定义。

DialogBox有一个changed方法,用于处理各子组件的改变。

引入DialogBox后,所有的逻辑就在同一个地方了。可理解性和可维护性也相应提高了。

DialogBox是一个抽象类,我们需要扩展使用。这里我们引入ArticlesDialogBox类来继承实现它。

这种模式就叫做中介者模式(mediator pattern)。

各个对象彼此不知道对方,他们只知道中介者,通过中介者各对象实现互相通话。

四人帮的命名是这样的。

Mediaor 相当于 DialogBox。

ConcreteMediator 相当于 ArticlesDialogBox。

Colleague 相当于 UIControl。

ConcreteColleague 相当于 TextBox, ListBoxt 等组件。

这里具体的Mediator类和具体的Colleague是依赖的。但这种依赖方向是没有任何问题的,对话框表单ConcreteMediator当然得了解组成它的组件ConcreteColleague有哪些。


代码实现

创建抽象类DialogBox,定义抽象方法changed,该抽象方法负责所有UIControl对象的更改逻辑。

修改UIControl类,添加属性owner,其类型为DialogBox,注意其修饰符为protected,这样才可以在继承类中被访问。

修改ListBox,初始化基类的DialogBox对象owner,在选择条目(setSelection)时,调用owner的changed。

同样的,TextBox、Button也类似地修改。

创建ArticlesDialogBox类,它继承于DialogBox,有articlesListBox、titleTextBox、saveButton等属性。

在changed方法里,判断UIControl类型,然后对相关的其他UIControl进行操作。

为了演示,我们也创建了一个simulateUserInteraction方法。

再到main方法里测试一下。

修改simulateUserInteraction,模拟清空titleTextBox和填充其他内容,确认测试结果符合预期。


使用观察者模式

我们看到,中介者模式下,所有控制逻辑都由changed方法完成,当对话框表单有很多UIControl组件时,changed方法将会变得比较复杂,会有很长的判断语句。

我们可以使用观察者模式来实现中介者模式。

观察者模式下,任何人都可以对Subject的变化感兴趣,他可以告诉Subject,嘿,我对你有兴趣,当你有变化时请通知我。

利用观察者模式实现中介者模式,UIControl相当于Subject的角色,而DialogBox相当于Observer的角色。使用这种结构,各个UIControl类不必再依赖于DialogBox。

我们修改代码实现。

添加接口Observer,定义接口的updated方法。

删除UIControl类的owner属性,添加attach方法,notifyObservers方法,以及observers属性。因为直接生成UIControl类的实例无意义,将其更改为抽象类,只允许其子类生成实例。

修改Button类,删除传递owner属性的构造函数,在setEnabled方法里,添加调用notifyObservers方法。

同样的,修改ListBox和TextBox。

至此,我们再也用不到DialogBox这个中介者了,删除。

再来修改ArticlesDialogBox。

ArticlesDialogBox不再继承DialogBox,删除原来实现的changed方法。

在构造函数里,将Observer注册到Subject,也就是将Observer注册到Button、ListBox、TextBox等UIControl对象。

以ListBox为例,在构造函数里,新建匿名的Observer的实现类,在其updated方法的实现中,调用之前实现的articleSelected方法。

这个实现没问题,不过我们可以简化。在Java里,只定义了一个方法的接口称为函数式接口,可以使用Lambda表达式简化。Lambda表达式主要用于简化函数写法,像这样:(参数)->{代码体},分离参数与代码执行体,在我们这个例子:

()->articleSelected();

由于articleSelected是已存在的方法,可以进一步利用Java的方法引用(::),注意引用的方法不加括号。

this::articleSelected

同样的,TextBox实例也可引用titleChanged方法,这样就极大地简化了代码,并可避免长串的if判断语句。

我们将有关接口或方法重命名。

重命名接口Observer为EventHandler,方法updated重命名为handle。

重命名UIControl类的attach方法为addEventHandler,observers列表重命名为eventHandlers,notifyObservers方法重命名为notifyEventHandlers。

改名之后,你是否觉得这些名字很熟悉?你肯定在很多GUI框架中见到过,但或许从未想过它们到底是怎么工作的,现在你就知道了。


小结

中介者模式可以实现互不依赖的各个类之间进行通信协作,我们也可以用观察者模式实现中介者模式。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注