“ 设计二十三式之中介模式”
01 意图
中介者是一种行为设计模式,可让您减少对象之间的混乱依赖关系。该模式限制了对象之间的直接通信,并迫使它们仅通过中介对象进行协作。
02 问题
假设您有一个用于创建和编辑客户资料的对话框。它由各种表单控件组成,例如文本字段、复选框、按钮等。
一些表单元素可能会与其他元素交互。例如,选择“我有一只狗”复选框可能会显示一个隐藏的文本字段,用于输入狗的名字。另一个例子是提交按钮,它必须在保存数据之前验证所有字段的值。
通过直接在表单元素的代码中实现此逻辑,您可以使这些元素的类更难在应用程序的其他形式中重用。例如,您将无法在另一个表单中使用该复选框类,因为它与狗的文本字段耦合。您可以使用渲染配置文件表单所涉及的所有类,或者根本不使用。
03 解决方案
中介者模式建议您应该停止要使彼此独立的组件之间的所有直接通信。相反,这些组件必须通过调用将调用重定向到适当组件的特殊中介对象来间接协作。因此,这些组件只依赖于一个中介类,而不是耦合到他们的几十个同事。
在我们的配置文件编辑表单示例中,对话框类本身可以充当中介。很可能,对话框类已经知道它的所有子元素,因此您甚至不需要在此类中引入新的依赖项。
最显着的变化发生在实际的表单元素上。让我们考虑提交按钮。以前,每次用户单击按钮时,都必须验证所有单个表单元素的值。现在它的唯一工作是通知对话框有关点击。收到此通知后,对话框本身会执行验证或将任务传递给各个元素。因此,按钮不再依赖于十几个表单元素,而是仅依赖于对话框类。
通过提取所有类型对话框的通用接口,您可以更进一步,使依赖关系更加松散。该接口将声明所有表单元素都可以用来通知对话框有关这些元素发生的事件的通知方法。因此,我们的提交按钮现在应该能够与实现该接口的任何对话框一起使用。
通过这种方式,中介者模式允许您将各种对象之间的复杂关系网络封装在单个中介者对象中。一个类的依赖项越少,修改、扩展或重用该类就越容易。
04 举个栗子
接近或离开机场控制区的飞机飞行员彼此之间不直接通信。取而代之的是,他们与一名空中交通管制员交谈,该空中交通管制员坐在飞机跑道附近的一座高塔中。如果没有空中交通管制员,飞行员将需要了解机场附近的每架飞机,并与其他数十名飞行员组成的委员会讨论着陆优先事项。这可能会使飞机失事统计数据飙升。
塔不需要控制整个飞行。它的存在只是为了在终端区域实施限制,因为那里涉及的参与者数量可能对飞行员来说是压倒性的。
05 结构实现
在本例中,中介者模式帮助您消除各种 UI 类之间的相互依赖关系:按钮、复选框和文本标签。
由用户触发的元素不会直接与其他元素通信,即使看起来应该如此。相反,该元素只需要让其中介者知道该事件,将任何上下文信息与该通知一起传递。
在此示例中,整个身份验证对话框充当中介。它知道具体元素应该如何协作并促进它们的间接交流。收到有关事件的通知后,对话框决定哪个元素应该处理该事件并相应地重定向调用。
// The mediator interface declares a method used by components
// to notify the mediator about various events. The mediator may
// react to these events and pass the execution to other
// components.
interface Mediator is
method notify(sender: Component, event: string)
// The concrete mediator class. The intertwined web of
// connections between individual components has been untangled
// and moved into the mediator.
class AuthenticationDialog implements Mediator is
private field title: string
private field loginOrRegisterChkBx: Checkbox
private field loginUsername, loginPassword: Textbox
private field registrationUsername, registrationPassword,
registrationEmail: Textbox
private field okBtn, cancelBtn: Button
constructor AuthenticationDialog() is
// Create all component objects and pass the current
// mediator into their constructors to establish links.
// When something happens with a component, it notifies the
// mediator. Upon receiving a notification, the mediator may
// do something on its own or pass the request to another
// component.
method notify(sender, event) is
if (sender == loginOrRegisterChkBx and event == "check")
if (loginOrRegisterChkBx.checked)
title = "Log in"
// 1. Show login form components.
// 2. Hide registration form components.
else
title = "Register"
// 1. Show registration form components.
// 2. Hide login form components
if (sender == okBtn && event == "click")
if (loginOrRegister.checked)
// Try to find a user using login credentials.
if (!found)
// Show an error message above the login
// field.
else
// 1. Create a user account using data from the
// registration fields.
// 2. Log that user in.
// ...
// Components communicate with a mediator using the mediator
// interface. Thanks to that, you can use the same components in
// other contexts by linking them with different mediator
// objects.
class Component is
field dialog: Mediator
constructor Component(dialog) is
this.dialog = dialog
method click() is
dialog.notify(this, "click")
method keypress() is
dialog.notify(this, "keypress")
// Concrete components don't talk to each other. They have only
// one communication channel, which is sending notifications to
// the mediator.
class Button extends Component is
// ...
class Textbox extends Component is
// ...
class Checkbox extends Component is
method check() is
dialog.notify(this, "check")
// ...
06 适用场景
-
当某些类与许多其他类紧密耦合而难以更改时,请使用中介者模式。
该模式允许您将类之间的所有关系提取到一个单独的类中,从而将特定组件的任何更改与其余组件隔离开来。
-
当您无法在不同的程序中重用组件时使用该模式,因为它过于依赖其他组件。
应用调解器后,各个组件将不知道其他组件。他们仍然可以通过中介对象相互通信,尽管是间接的。要在不同的应用程序中重用组件,您需要为其提供一个新的中介类。
-
当您发现自己创建大量组件子类只是为了在各种上下文中重用一些基本行为时,请使用调解器。
由于组件之间的所有关系都包含在中介中,因此很容易通过引入新的中介类来为这些组件定义全新的协作方式,而无需更改组件本身。
07 如何实施
确定一组紧密耦合的类,这些类将受益于更加独立(例如,为了更容易维护或更简单地重用这些类)。
-
声明调解器接口并描述调解器和各种组件之间所需的通信协议。在大多数情况下,从组件接收通知的单一方法就足够了。
当您想在不同的上下文中重用组件类时,此接口至关重要。只要组件通过通用接口与其调解器一起工作,您就可以将组件与调解器的不同实现链接。
实现具体的中介类。此类将受益于存储对其管理的所有组件的引用。
您可以更进一步,让中介者负责组件对象的创建和销毁。在此之后,中介可能类似于工厂或门面。
组件应该存储对中介对象的引用。连接通常在组件的构造函数中建立,其中中介对象作为参数传递。
更改组件的代码,以便它们调用中介的通知方法,而不是其他组件上的方法。将涉及调用其他组件的代码提取到中介类中。每当中介收到来自该组件的通知时,执行此代码。
08 如何实施