0%

译-设计模式-行为模式之Mediator

意图

Meditor是行为模式的一种,允许你定一个对象来封装一些对象的关联关系,以使这些对象相互独立。

问题

你有一个对话框用来编辑用户的配置。它是由TextEdit、Checkboxes、Button等组成的表单。

表单中元素会相互影响。比如,“我有一条狗”的复选框应当展示隐藏的文本框用来输入狗的名字。另外一个例子是提交按钮在保存数据钱必须娇艳所有字段的数据。

把这些逻辑直接放在表单元素代码中,使得这些类难以被app其他表单复用。比如,你不能使用另一个表单中的复选框,因为它和狗名字的文本框紧密耦合。

解决

Mediator模式阻止单个组件间的直接交。取而代之,他们发行请求到中间人对象,这个对象知道如何引导请求。组件摆脱了相互间的依赖,只和中间人对象有关联。

在我们的例子中,对话框类可以当这个中间人。可能,甚至不需要在类中添加任何新的依赖关系,因为对话框已经知道它的子表单元素。

表单元素代码会发生重大的变化。比如,提交按钮之前必须在按钮点击后校验变淡元素,但是现在它只需要发出被点击的通知即可。收到提交通知后,对话框自己完成所有的校验。通过这种方式,按钮将只需要依赖对话框类。

你甚至可以更进一步使得依赖更加宽松,为所有对话框抽离出一个通用接口。这个接口需要声明一个方法去通知对应表单元素相关的事件。这样,我们的提交按钮就可以和任意一个遵循这个接口的的对话框通信。

通过这种方式,Mediator模式包装各个组件的关联关系到一个单独的中间人对象中。类的依赖越少,修改、扩展或重用它就越容易。

现实世界类比

空中管制

塔台控制机场很形象的展示了Mediator模式。飞行员在降落或者起飞时和塔台通信,而不是和其他飞行员直接通信。塔台控制哪架飞机可以起飞或者降落。

注意,塔台并不控制整个航程,只对终端区域进行管制。

结构

structure

  1. Components(组件)是包含业务逻辑程序的各种对象。每个组件都持有一个中间人的
    引用。组件不关心中间人的实际类型,因为他们通过Mediator接口协作。这允许在其他程序中复用组件,只要改变他们的关联中间人即可。

    组件也不关心其他组件。当它们的状态改变时,它们只需要通知中间人。中间人知道哪些组件需要响应这个事件。

  2. Mediator声明了和Component通信的接口,它通常含有一个方法。组件组件使用这个方法通知中间人他们发生了一个重要的事件。组件把他们自己当作事件的一部分传给通知方法。

    中间人接口是已存在组件可被其他程序崇重用的关键。你需要做的是提供一个新的中间人来控制组件如何在新的上下文中同其他组件协作。

  3. Concrete Mediator(具体的中间人)封装了各个组件间的关系。中间人通常持有它它管理的所有组件的引用。

    具体的中间人常常和他们的组件紧密耦合。当中间人收到一个通知,他可以轻松的识别出发送者。它也知道哪个组件应当被触发。但是对组件而言,它就是一个黑盒。发送者不知道谁来处理它的事件,接受者也不知道谁发出的这个事件。

伪代码

在这个例子中,Mediator模式帮助消除UI类间的相互依赖:按钮、复选框和文本标签。当用户与元素进行交互时,他们不直接进行通信,而是把这个事件通知给中间人。

认证对话框扮演了这个中间人。它知道具体的要素应该如何协作并促进其合作。在接收到一个通知事件后,他把事件传递给适当的组件来执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// The mediator interface.
interface Mediator is
method notify(sender: Component, event: string)


// The concrete mediator class. All chaotic communications
// between concrete components were extracted to the
// mediator class.
class AuthenticationDialog implements Mediator is
private field title: string
private field loginOrRegister: Checkbox
private field loginUsername, loginPassword: Textbox
private field registrationUsername, registrationPassword
private field registrationEmail: Textbox
private field ok, cancel: Button

constructor AuthenticationDialog() is
Create all component objects.
Link meditor with components via constructor params.

// 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 == loginOrRegister and event == "check")
if (loginOrRegister.checked)
title = "Log in"
Show login components
Hide registration components.
else
title = "Register"
Show registration components.
Hide login components
if (sender == ok and event == "click")
if (loginOrRegister.checked)
Try to find user using login credentials.
if (!found)
Show errors over login fields.
else
Create account using registration fields.
Log 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 a
// different mediator object.
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 do not 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")
// ...

适用性

  • 当组件间依赖混乱,某个组件简单的改变导致其关联的组件都需要改变。

    中间人把类之间的关系抽离到一个单独的类中,使得一个组件的改变对其他代码是隔离的。

  • 当你无法在不同的程序中复用一个组件,因为它太依赖其他组件。

    使用了中间人后,组件个体不在关心其他组件。他们通过中间人间接通信。在不同的app中复用一个组件只需要创建一个新的中间人类。

  • 当你必须创建组件的多个子类但只是为了在不同的上下文中使用相同的组件时。

    中间人封装了组件之间的关系。因此,完全可以创建一个新的中间人子类为同一群组件定义一个新的关系。

如何实现

  1. 确定一组紧密联系的类,他们如果相互更加独立,我们能从中获得收益。

  2. 创建Mediator接口并且声明其和各个组件间的通讯协议。大多情况下,定义一个从组件接收通知的方法就足够了。

  3. 在具体的Mediator中实现这个方法。Mediator类最好持有它管理的所有组件的引用。

  4. 你甚至可以把创建组件的代码挪到Mediator中。这将使中间人变成一个工厂。

  5. 组件也应当持有中间人的引用。这个链接通常在组件的构造方法中建立,中间人对象是组件构造方法的一部分。

  6. 改变组件的代码以便当一些事情发生时它能够调用中间人的通知方法。剥离真正的业务代码到Mediator类中,并在其接收到组件的通知后执行它。

优点

  • 减少一个程序中组件的耦合。

  • 允许复用单个组件。

  • 集中各个组件之间的通信。

缺点

  • Mediator可能会发展成God Object。

和其他模式的关系

  • CoR、Command、Mediator和Observer处理各种方式的发送者和接受者:

    • CoR让请求沿着一个动态的潜在接受者链条传递直到某个接口者处理它。

    • Command建立了发送者和接受者的单项链接。

    • Mediator让发送者和接受者相互间简介关联。

    • Observer在同一时间把请求发送给所有关心的接收者,但是允许他们动态的订阅和取消订阅之后的请求。

  • Mediator和Facade在抽象已存在类的功能这点上很像。Mediator 抽象/集中同事间的任意通信。它通常“添加值”并且是同事对象所知道/参考的。相比之下,Facade为子系统定义了一个更简单的接口,它不会增加新的功能,而且子系统类也不知道它的存在。

  • Mediator和Observer模式的区别常常很模糊。在大多数情况下,模式间是竞争的,但有时他们可以合作。

    Mediator调模式的主要目标是消除一组系统组件之间的相互依赖关系。所有组件都变成独立的对象。另一方面,Observer的主要目的是建立对象间动态的单向链接,其中一些随想作为其他对象的下属。

    有一个非常受欢迎的Mediator实现依赖于Observer模式。中间人对象扮演发布者,所有的同事组件扮演订阅者。他们动态的订阅和取消订阅中间人的事件。这种实现方式看起来和两种模式都很像。

    但是Mediator模式可以有不同的实现方式,比如所有的组件都永久连接到形同的中间人对象。这种实现看起来不像Observer,但仍是Mediator模式的一个例子。

    现在想象另一个程序,其中所有的组件都是发布者,并允许彼此动态链接。这里没有一个中央中间人对象,而是一个分布式的观察员系统。

小结

Java代码中Mediator模式最流行的用法是促进应用程序的GUI组件之间的通信。Mediator的同义词是MVC模式的控制器部分。

在Java核心库中有以下使用该模式的例子:

  • java.util.Timer (所有的scheduleXXX()方法)

  • java.util.concurrent.Executor#execute()

  • java.util.concurrent.ExecutorService (invokeXXX()和submit()方法)

  • java.util.concurrent.ScheduledExecutorService (所有的 scheduleXXX()方法)

  • java.lang.reflect.Method#invoke()

参考

翻译整理自:https://refactoring.guru/design-patterns/mediator