https://refactoringguru.cn/design-patterns/mediator

行为模式-中介者模式

亦称: 调解人、控制器、Intermediary、Controller、Mediator

意图

中介者模式是一种行为设计模式, 能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作。

问题

假如你有一个创建和修改客户资料的对话框, 它由各种控件组成, 例如文本框 (Text­Field)、 复选框 (Checkbox) 和按钮 (Button) 等。

用户界面中各元素间的关系会随程序发展而变得混乱。

某些表单元素可能会直接进行互动。 例如, 选中 “我有一只狗” 复选框后可能会显示一个隐藏文本框用于输入狗狗的名字。 另一个例子是提交按钮必须在保存数据前校验所有输入内容。

元素间存在许多关联。 因此, 对某些元素进行修改可能会影响其他元素。

如果直接在表单元素代码中实现业务逻辑, 你将很难在程序其他表单中复用这些元素类。 例如, 由于复选框类与狗狗的文本框相耦合, 所以将无法在其他表单中使用它。 你要么使用渲染资料表单时用到的所有类, 要么一个都不用。

解决方案

中介者模式建议你停止组件之间的直接交流并使其相互独立。 这些组件必须调用特殊的中介者对象, 通过中介者对象重定向调用行为, 以间接的方式进行合作。 最终, 组件仅依赖于一个中介者类, 无需与多个其他组件相耦合。

在资料编辑表单的例子中, 对话框 (Dialog) 类本身将作为中介者, 其很可能已知自己所有的子元素, 因此你甚至无需在该类中引入新的依赖关系。

UI 元素必须通过中介者对象进行间接沟通。

绝大部分重要的修改都在实际表单元素中进行。 让我们想想提交按钮。 之前, 当用户点击按钮后, 它必须对所有表单元素数值进行校验。 而现在它的唯一工作是将点击事件通知给对话框。 收到通知后, 对话框可以自行校验数值或将任务委派给各元素。 这样一来, 按钮不再与多个表单元素相关联, 而仅依赖于对话框类。

你还可以为所有类型的对话框抽取通用接口, 进一步削弱其依赖性。 接口中将声明一个所有表单元素都能使用的通知方法, 可用于将元素中发生的事件通知给对话框。 这样一来, 所有实现了该接口的对话框都能使用这个提交按钮了。

采用这种方式, 中介者模式让你能在单个中介者对象中封装多个对象间的复杂关系网。 类所拥有的依赖关系越少, 就越易于修改、 扩展或复用。

真实世界类比

飞行器驾驶员之间不会通过相互沟通来决定下一架降落的飞机。 所有沟通都通过控制塔台进行。

飞行器驾驶员们在靠近或离开空中管制区域时不会直接相互交流。 但他们会与飞机跑道附近, 塔台中的空管员通话。 如果没有空管员, 驾驶员就需要留意机场附近的所有飞机, 并与数十位飞行员组成的委员会讨论降落顺序。 那恐怕会让飞机坠毁的统计数据一飞冲天吧。

塔台无需管制飞行全程, 只需在航站区加强管控即可, 因为该区域的决策参与者数量对于飞行员来说实在太多了。

中介者模式结构

  1. 组件 (Component) 是各种包含业务逻辑的类。 每个组件都有一个指向中介者的引用, 该引用被声明为中介者接口类型。 组件不知道中介者实际所属的类, 因此你可通过将其连接到不同的中介者以使其能在其他程序中复用。

  2. 中介者 (Mediator) 接口声明了与组件交流的方法, 但通常仅包括一个通知方法。 组件可将任意上下文 (包括自己的对象) 作为该方法的参数, 只有这样接收组件和发送者类之间才不会耦合。

  3. 具体中介者 (Concrete Mediator) 封装了多种组件间的关系。 具体中介者通常会保存所有组件的引用并对其进行管理, 甚至有时会对其生命周期进行管理。

  4. 组件并不知道其他组件的情况。 如果组件内发生了重要事件, 它只能通知中介者。 中介者收到通知后能轻易地确定发送者, 这或许已足以判断接下来需要触发的组件了。

    对于组件来说, 中介者看上去完全就是一个黑箱。 发送者不知道最终会由谁来处理自己的请求, 接收者也不知道最初是谁发出了请求。

伪代码

在本例中, 中介者模式可帮助你减少各种 UI 类 (按钮、 复选框和文本标签) 之间的相互依赖关系。

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
// 中介者接口声明了一个能让组件将各种事件通知给中介者的方法。中介者可对这
// 些事件做出响应并将执行工作传递给其他组件。
interface Mediator is
method notify(sender: Component, event: string)


// 具体中介者类可解开各组件之间相互交叉的连接关系并将其转移到中介者中。
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
// 创建所有组件对象并将当前中介者传递给其构造函数以建立连接。

// 当组件中有事件发生时,它会通知中介者。中介者接收到通知后可自行处理,
// 也可将请求传递给另一个组件。
method notify(sender, event) is
if (sender == loginOrRegisterChkBx and event == "check")
if (loginOrRegisterChkBx.checked)
title = "登录"
// 1. 显示登录表单组件。
// 2. 隐藏注册表单组件。
else
title = "注册"
// 1. 显示注册表单组件。
// 2. 隐藏登录表单组件。

if (sender == okBtn && event == "click")
if (loginOrRegister.checked)
// 尝试找到使用登录信息的用户。
if (!found)
// 在登录字段上方显示错误信息。
else
// 1. 使用注册字段中的数据创建用户账号。
// 2. 完成用户登录工作。 …


// 组件会使用中介者接口与中介者进行交互。因此只需将它们与不同的中介者连接
// 起来,你就能在其他情境中使用这些组件了。
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")

// 具体组件之间无法进行交流。它们只有一个交流渠道,那就是向中介者发送通知。
class Button extends Component is
// ...

class Textbox extends Component is
// ...

class Checkbox extends Component is
method check() is
dialog.notify(this, "check")
// ...

中介者模式适合应用场景

当一些对象和其他对象紧密耦合以致难以对其进行修改时,可使用中介者模式。

该模式让你将对象间的所有关系抽取成为一个单独的类, 以使对于特定组件的修改工作独立于其他组件。

当组件因过于依赖其他组件而无法在不同应用中复用时,可使用中介者模式。

应用中介者模式后, 每个组件不再知晓其他组件的情况。 尽管这些组件无法直接交流, 但它们仍可通过中介者对象进行间接交流。 如果你希望在不同应用中复用一个组件, 则需要为其提供一个新的中介者类。

如果为了能在不同情景下复用一些基本行为,导致你需要被迫创建大量组件子类时,可使用中介者模式。

由于所有组件间关系都被包含在中介者中, 因此你无需修改组件就能方便地新建中介者类以定义新的组件合作方式。

实现方式

  1. 找到一组当前紧密耦合, 且提供其独立性能带来更大好处的类 (例如更易于维护或更方便复用)。
  2. 声明中介者接口并描述中介者和各种组件之间所需的交流接口。 在绝大多数情况下, 一个接收组件通知的方法就足够了。 如果你希望在不同情景下复用组件类, 那么该接口将非常重要。 只要组件使用通用接口与其中介者合作, 你就能将该组件与不同实现中的中介者进行连接。
  3. 实现具体中介者类。 该类可从自行保存其下所有组件的引用中受益。
  4. 你可以更进一步, 让中介者负责组件对象的创建和销毁。 此后, 中介者可能会与工厂外观类似。
  5. 组件必须保存对于中介者对象的引用。 该连接通常在组件的构造函数中建立, 该函数会将中介者对象作为参数传递。
  6. 修改组件代码, 使其可调用中介者的通知方法, 而非其他组件的方法。 然后将调用其他组件的代码抽取到中介者类中, 并在中介者接收到该组件通知时执行这些代码。

中介者模式优缺点

优点

  • 单一职责原则。 你可以将多个组件间的交流抽取到同一位置, 使其更易于理解和维护。
  • 开闭原则。 你无需修改实际组件就能增加新的中介者。
  • 你可以减轻应用中多个组件间的耦合情况。
  • 你可以更方便地复用各个组件。

缺点

  • 一段时间后, 中介者可能会演化成为上帝对象

与其他模式的关系

  • 责任链模式命令模式中介者模式观察者模式用于处理请求发送者和接收者之间的不同连接方式:

    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 外观模式中介者的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。

    • 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。
    • 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。
  • 中介者观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。

    中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。

    有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时, 它可能看上去与观察者非常相似。

    当你感到疑惑时, 记住可以采用其他方式来实现中介者。 例如, 你可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同, 但这仍是一种中介者模式。

    假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象, 而只有一些分布式的观察者。

代码示例

中介者是一种行为设计模式, 让程序组件通过特殊的中介者对象进行间接沟通, 达到减少组件之间依赖关系的目的。

中介者能使得程序更易于修改和扩展, 而且能更方便地对独立的组件进行复用, 因为它们不再依赖于很多其他的类。

在 C# 中使用模式

复杂度: ★★☆

流行度: ★★☆

使用示例: 中介者模式在 C# 代码中最常用于帮助程序 GUI 组件之间的通信。 在 MVC 模式中, 控制器是中介者的同义词。

概念示例

本例说明了中介者设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

Program.cs: 概念示例

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
using System;

namespace RefactoringGuru.DesignPatterns.Mediator.Conceptual
{
// 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.
public interface IMediator
{
void Notify(object sender, string ev);
}

// Concrete Mediators implement cooperative behavior by coordinating several
// components.
class ConcreteMediator : IMediator
{
private Component1 _component1;

private Component2 _component2;

public ConcreteMediator(Component1 component1, Component2 component2)
{
this._component1 = component1;
this._component1.SetMediator(this);
this._component2 = component2;
this._component2.SetMediator(this);
}

public void Notify(object sender, string ev)
{
if (ev == "A")
{
Console.WriteLine("Mediator reacts on A and triggers folowing operations:");
this._component2.DoC();
}
if (ev == "D")
{
Console.WriteLine("Mediator reacts on D and triggers following operations:");
this._component1.DoB();
this._component2.DoC();
}
}
}

// The Base Component provides the basic functionality of storing a
// mediator's instance inside component objects.
class BaseComponent
{
protected IMediator _mediator;

public BaseComponent(IMediator mediator = null)
{
this._mediator = mediator;
}

public void SetMediator(IMediator mediator)
{
this._mediator = mediator;
}
}

// Concrete Components implement various functionality. They don't depend on
// other components. They also don't depend on any concrete mediator
// classes.
class Component1 : BaseComponent
{
public void DoA()
{
Console.WriteLine("Component 1 does A.");

this._mediator.Notify(this, "A");
}

public void DoB()
{
Console.WriteLine("Component 1 does B.");

this._mediator.Notify(this, "B");
}
}

class Component2 : BaseComponent
{
public void DoC()
{
Console.WriteLine("Component 2 does C.");

this._mediator.Notify(this, "C");
}

public void DoD()
{
Console.WriteLine("Component 2 does D.");

this._mediator.Notify(this, "D");
}
}

class Program
{
static void Main(string[] args)
{
// The client code.
Component1 component1 = new Component1();
Component2 component2 = new Component2();
new ConcreteMediator(component1, component2);

Console.WriteLine("Client triggets operation A.");
component1.DoA();

Console.WriteLine();

Console.WriteLine("Client triggers operation D.");
component2.DoD();
}
}
}

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.

Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.

在 C++ 中使用模式

复杂度: ★★☆

流行度: ★★☆

使用示例: 中介者模式在 C++ 代码中最常用于帮助程序 GUI 组件之间的通信。 在 MVC 模式中, 控制器是中介者的同义词。

概念示例

本例说明了中介者设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

main.cc: 概念示例

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include <iostream>
#include <string>
/**
* 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.
*/
class BaseComponent;
class Mediator {
public:
virtual void Notify(BaseComponent *sender, std::string event) const = 0;
};

/**
* The Base Component provides the basic functionality of storing a mediator's
* instance inside component objects.
*/
class BaseComponent {
protected:
Mediator *mediator_;

public:
BaseComponent(Mediator *mediator = nullptr) : mediator_(mediator) {
}
void set_mediator(Mediator *mediator) {
this->mediator_ = mediator;
}
};

/**
* Concrete Components implement various functionality. They don't depend on
* other components. They also don't depend on any concrete mediator classes.
*/
class Component1 : public BaseComponent {
public:
void DoA() {
std::cout << "Component 1 does A.\n";
this->mediator_->Notify(this, "A");
}
void DoB() {
std::cout << "Component 1 does B.\n";
this->mediator_->Notify(this, "B");
}
};

class Component2 : public BaseComponent {
public:
void DoC() {
std::cout << "Component 2 does C.\n";
this->mediator_->Notify(this, "C");
}
void DoD() {
std::cout << "Component 2 does D.\n";
this->mediator_->Notify(this, "D");
}
};

/**
* Concrete Mediators implement cooperative behavior by coordinating several
* components.
*/
class ConcreteMediator : public Mediator {
private:
Component1 *component1_;
Component2 *component2_;

public:
ConcreteMediator(Component1 *c1, Component2 *c2) : component1_(c1), component2_(c2) {
this->component1_->set_mediator(this);
this->component2_->set_mediator(this);
}
void Notify(BaseComponent *sender, std::string event) const override {
if (event == "A") {
std::cout << "Mediator reacts on A and triggers following operations:\n";
this->component2_->DoC();
}
if (event == "D") {
std::cout << "Mediator reacts on D and triggers following operations:\n";
this->component1_->DoB();
this->component2_->DoC();
}
}
};

/**
* The client code.
*/

void ClientCode() {
Component1 *c1 = new Component1;
Component2 *c2 = new Component2;
ConcreteMediator *mediator = new ConcreteMediator(c1, c2);
std::cout << "Client triggers operation A.\n";
c1->DoA();
std::cout << "\n";
std::cout << "Client triggers operation D.\n";
c2->DoD();

delete c1;
delete c2;
delete mediator;
}

int main() {
ClientCode();
return 0;
}

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.

Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.

在 Java 中使用模式

复杂度: ★★☆

流行度: ★★☆

使用示例: 中介者模式在 Java 代码中最常用于帮助程序 GUI 组件之间的通信。 在 MVC 模式中, 控制器是中介者的同义词。

下面是核心 Java 程序库中该模式的一些示例:

笔记程序

本例展示了如何将许多 GUI 元素组织起来, 使其在中介者的帮助下无需相互依赖就能合作。

components: Colleague classes

components/Component.java

1
2
3
4
5
6
7
8
9
10
11
package refactoring_guru.mediator.example.components;

import refactoring_guru.mediator.example.mediator.Mediator;

/**
* Common component interface.
*/
public interface Component {
void setMediator(Mediator mediator);
String getName();
}

components/AddButton.java

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
package refactoring_guru.mediator.example.components;

import refactoring_guru.mediator.example.mediator.Mediator;
import refactoring_guru.mediator.example.mediator.Note;

import javax.swing.*;
import java.awt.event.ActionEvent;

/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
public class AddButton extends JButton implements Component {
private Mediator mediator;

public AddButton() {
super("Add");
}

@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}

@Override
protected void fireActionPerformed(ActionEvent actionEvent) {
mediator.addNewNote(new Note());
}

@Override
public String getName() {
return "AddButton";
}
}

components/DeleteButton.java

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
package refactoring_guru.mediator.example.components;

import refactoring_guru.mediator.example.mediator.Mediator;

import javax.swing.*;
import java.awt.event.ActionEvent;

/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
public class DeleteButton extends JButton implements Component {
private Mediator mediator;

public DeleteButton() {
super("Del");
}

@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}

@Override
protected void fireActionPerformed(ActionEvent actionEvent) {
mediator.deleteNote();
}

@Override
public String getName() {
return "DelButton";
}
}

components/Filter.java

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
package refactoring_guru.mediator.example.components;

import refactoring_guru.mediator.example.mediator.Mediator;
import refactoring_guru.mediator.example.mediator.Note;

import javax.swing.*;
import java.awt.event.KeyEvent;
import java.util.ArrayList;

/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
public class Filter extends JTextField implements Component {
private Mediator mediator;
private ListModel listModel;

public Filter() {}

@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}

@Override
protected void processComponentKeyEvent(KeyEvent keyEvent) {
String start = getText();
searchElements(start);
}

public void setList(ListModel listModel) {
this.listModel = listModel;
}

private void searchElements(String s) {
if (listModel == null) {
return;
}

if (s.equals("")) {
mediator.setElementsList(listModel);
return;
}

ArrayList<Note> notes = new ArrayList<>();
for (int i = 0; i < listModel.getSize(); i++) {
notes.add((Note) listModel.getElementAt(i));
}
DefaultListModel<Note> listModel = new DefaultListModel<>();
for (Note note : notes) {
if (note.getName().contains(s)) {
listModel.addElement(note);
}
}
mediator.setElementsList(listModel);
}

@Override
public String getName() {
return "Filter";
}
}

components/List.java

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
75
76
77
78
79
package refactoring_guru.mediator.example.components;

import refactoring_guru.mediator.example.mediator.Mediator;
import refactoring_guru.mediator.example.mediator.Note;

import javax.swing.*;

/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
@SuppressWarnings("unchecked")
public class List extends JList implements Component {
private Mediator mediator;
private final DefaultListModel LIST_MODEL;

public List(DefaultListModel listModel) {
super(listModel);
this.LIST_MODEL = listModel;
setModel(listModel);
this.setLayoutOrientation(JList.VERTICAL);
Thread thread = new Thread(new Hide(this));
thread.start();
}

@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}

public void addElement(Note note) {
LIST_MODEL.addElement(note);
int index = LIST_MODEL.size() - 1;
setSelectedIndex(index);
ensureIndexIsVisible(index);
mediator.sendToFilter(LIST_MODEL);
}

public void deleteElement() {
int index = this.getSelectedIndex();
try {
LIST_MODEL.remove(index);
mediator.sendToFilter(LIST_MODEL);
} catch (ArrayIndexOutOfBoundsException ignored) {}
}

public Note getCurrentElement() {
return (Note)getSelectedValue();
}

@Override
public String getName() {
return "List";
}

private class Hide implements Runnable {
private List list;

Hide(List list) {
this.list = list;
}

@Override
public void run() {
while (true) {
try {
Thread.sleep(300);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
if (list.isSelectionEmpty()) {
mediator.hideElements(true);
} else {
mediator.hideElements(false);
}
}
}
}
}

components/SaveButton.java

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
package refactoring_guru.mediator.example.components;

import refactoring_guru.mediator.example.mediator.Mediator;

import javax.swing.*;
import java.awt.event.ActionEvent;

/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
public class SaveButton extends JButton implements Component {
private Mediator mediator;

public SaveButton() {
super("Save");
}

@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}

@Override
protected void fireActionPerformed(ActionEvent actionEvent) {
mediator.saveChanges();
}

@Override
public String getName() {
return "SaveButton";
}
}

components/TextBox.java

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
package refactoring_guru.mediator.example.components;

import refactoring_guru.mediator.example.mediator.Mediator;

import javax.swing.*;
import java.awt.event.KeyEvent;

/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
public class TextBox extends JTextArea implements Component {
private Mediator mediator;

@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}

@Override
protected void processComponentKeyEvent(KeyEvent keyEvent) {
mediator.markNote();
}

@Override
public String getName() {
return "TextBox";
}
}

components/Title.java

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
package refactoring_guru.mediator.example.components;

import refactoring_guru.mediator.example.mediator.Mediator;

import javax.swing.*;
import java.awt.event.KeyEvent;

/**
* Concrete components don't talk with each other. They have only one
* communication channel–sending requests to the mediator.
*/
public class Title extends JTextField implements Component {
private Mediator mediator;

@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}

@Override
protected void processComponentKeyEvent(KeyEvent keyEvent) {
mediator.markNote();
}

@Override
public String getName() {
return "Title";
}
}

mediator

mediator/Mediator.java: 定义通用的中介者接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package refactoring_guru.mediator.example.mediator;

import refactoring_guru.mediator.example.components.Component;

import javax.swing.*;

/**
* Common mediator interface.
*/
public interface Mediator {
void addNewNote(Note note);
void deleteNote();
void getInfoFromList(Note note);
void saveChanges();
void markNote();
void clear();
void sendToFilter(ListModel listModel);
void setElementsList(ListModel list);
void registerComponent(Component component);
void hideElements(boolean flag);
void createGUI();
}

mediator/Editor.java: 具体中介者

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package refactoring_guru.mediator.example.mediator;

import refactoring_guru.mediator.example.components.*;
import refactoring_guru.mediator.example.components.Component;
import refactoring_guru.mediator.example.components.List;

import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.*;

/**
* Concrete mediator. All chaotic communications between concrete components
* have been extracted to the mediator. Now components only talk with the
* mediator, which knows who has to handle a request.
*/
public class Editor implements Mediator {
private Title title;
private TextBox textBox;
private AddButton add;
private DeleteButton del;
private SaveButton save;
private List list;
private Filter filter;

private JLabel titleLabel = new JLabel("Title:");
private JLabel textLabel = new JLabel("Text:");
private JLabel label = new JLabel("Add or select existing note to proceed...");

/**
* Here the registration of components by the mediator.
*/
@Override
public void registerComponent(Component component) {
component.setMediator(this);
switch (component.getName()) {
case "AddButton":
add = (AddButton)component;
break;
case "DelButton":
del = (DeleteButton)component;
break;
case "Filter":
filter = (Filter)component;
break;
case "List":
list = (List)component;
this.list.addListSelectionListener(listSelectionEvent -> {
Note note = (Note)list.getSelectedValue();
if (note != null) {
getInfoFromList(note);
} else {
clear();
}
});
break;
case "SaveButton":
save = (SaveButton)component;
break;
case "TextBox":
textBox = (TextBox)component;
break;
case "Title":
title = (Title)component;
break;
}
}

/**
* Various methods to handle requests from particular components.
*/
@Override
public void addNewNote(Note note) {
title.setText("");
textBox.setText("");
list.addElement(note);
}

@Override
public void deleteNote() {
list.deleteElement();
}

@Override
public void getInfoFromList(Note note) {
title.setText(note.getName().replace('*', ' '));
textBox.setText(note.getText());
}

@Override
public void saveChanges() {
try {
Note note = (Note) list.getSelectedValue();
note.setName(title.getText());
note.setText(textBox.getText());
list.repaint();
} catch (NullPointerException ignored) {}
}

@Override
public void markNote() {
try {
Note note = list.getCurrentElement();
String name = note.getName();
if (!name.endsWith("*")) {
note.setName(note.getName() + "*");
}
list.repaint();
} catch (NullPointerException ignored) {}
}

@Override
public void clear() {
title.setText("");
textBox.setText("");
}

@Override
public void sendToFilter(ListModel listModel) {
filter.setList(listModel);
}

@SuppressWarnings("unchecked")
@Override
public void setElementsList(ListModel list) {
this.list.setModel(list);
this.list.repaint();
}

@Override
public void hideElements(boolean flag) {
titleLabel.setVisible(!flag);
textLabel.setVisible(!flag);
title.setVisible(!flag);
textBox.setVisible(!flag);
save.setVisible(!flag);
label.setVisible(flag);
}

@Override
public void createGUI() {
JFrame notes = new JFrame("Notes");
notes.setSize(960, 600);
notes.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JPanel left = new JPanel();
left.setBorder(new LineBorder(Color.BLACK));
left.setSize(320, 600);
left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS));
JPanel filterPanel = new JPanel();
filterPanel.add(new JLabel("Filter:"));
filter.setColumns(20);
filterPanel.add(filter);
filterPanel.setPreferredSize(new Dimension(280, 40));
JPanel listPanel = new JPanel();
list.setFixedCellWidth(260);
listPanel.setSize(320, 470);
JScrollPane scrollPane = new JScrollPane(list);
scrollPane.setPreferredSize(new Dimension(275, 410));
listPanel.add(scrollPane);
JPanel buttonPanel = new JPanel();
add.setPreferredSize(new Dimension(85, 25));
buttonPanel.add(add);
del.setPreferredSize(new Dimension(85, 25));
buttonPanel.add(del);
buttonPanel.setLayout(new FlowLayout());
left.add(filterPanel);
left.add(listPanel);
left.add(buttonPanel);
JPanel right = new JPanel();
right.setLayout(null);
right.setSize(640, 600);
right.setLocation(320, 0);
right.setBorder(new LineBorder(Color.BLACK));
titleLabel.setBounds(20, 4, 50, 20);
title.setBounds(60, 5, 555, 20);
textLabel.setBounds(20, 4, 50, 130);
textBox.setBorder(new LineBorder(Color.DARK_GRAY));
textBox.setBounds(20, 80, 595, 410);
save.setBounds(270, 535, 80, 25);
label.setFont(new Font("Verdana", Font.PLAIN, 22));
label.setBounds(100, 240, 500, 100);
right.add(label);
right.add(titleLabel);
right.add(title);
right.add(textLabel);
right.add(textBox);
right.add(save);
notes.setLayout(null);
notes.getContentPane().add(left);
notes.getContentPane().add(right);
notes.setResizable(false);
notes.setLocationRelativeTo(null);
notes.setVisible(true);
}
}

mediator/Note.java: 笔记类

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
package refactoring_guru.mediator.example.mediator;

/**
* Note class.
*/
public class Note {
private String name;
private String text;

public Note() {
name = "New note";
}

public void setName(String name) {
this.name = name;
}

public void setText(String text) {
this.text = text;
}

public String getName() {
return name;
}

public String getText() {
return text;
}

@Override
public String toString() {
return name;
}
}

Demo.java: 初始化代码

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
package refactoring_guru.mediator.example;

import refactoring_guru.mediator.example.components.*;
import refactoring_guru.mediator.example.mediator.Editor;
import refactoring_guru.mediator.example.mediator.Mediator;

import javax.swing.*;

/**
* Demo class. Everything comes together here.
*/
public class Demo {
public static void main(String[] args) {
Mediator mediator = new Editor();

mediator.registerComponent(new Title());
mediator.registerComponent(new TextBox());
mediator.registerComponent(new AddButton());
mediator.registerComponent(new DeleteButton());
mediator.registerComponent(new SaveButton());
mediator.registerComponent(new List(new DefaultListModel()));
mediator.registerComponent(new Filter());

mediator.createGUI();
}
}

OutputDemo.png: 执行结果

在 PHP 中使用模式

复杂度: ★★☆

流行度: ★★☆

使用示例: 纯粹的中介者模式实现在 PHP 中并不常见, 在其他语言 (例如 Java 或 C#) 中则专门在 GUI 上使用。 PHP 应用可能会包含数十个组件, 但是它们极少会在单次对话中直接通信。

但是, 许多 PHP 框架中的事件分发系统或 MVC 控制器的一些实现仍会使用中介者模式。

概念示例

本例说明了中介者设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 PHP 应用案例。

index.php: 概念示例

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<?php

namespace RefactoringGuru\Mediator\Conceptual;

/**
* 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
{
public function notify(object $sender, string $event): void;
}

/**
* Concrete Mediators implement cooperative behavior by coordinating several
* components.
*/
class ConcreteMediator implements Mediator
{
private $component1;

private $component2;

public function __construct(Component1 $c1, Component2 $c2)
{
$this->component1 = $c1;
$this->component1->setMediator($this);
$this->component2 = $c2;
$this->component2->setMediator($this);
}

public function notify(object $sender, string $event): void
{
if ($event == "A") {
echo "Mediator reacts on A and triggers following operations:\n";
$this->component2->doC();
}

if ($event == "D") {
echo "Mediator reacts on D and triggers following operations:\n";
$this->component1->doB();
$this->component2->doC();
}
}
}

/**
* The Base Component provides the basic functionality of storing a mediator's
* instance inside component objects.
*/
class BaseComponent
{
protected $mediator;

public function __construct(Mediator $mediator = null)
{
$this->mediator = $mediator;
}

public function setMediator(Mediator $mediator): void
{
$this->mediator = $mediator;
}
}

/**
* Concrete Components implement various functionality. They don't depend on
* other components. They also don't depend on any concrete mediator classes.
*/
class Component1 extends BaseComponent
{
public function doA(): void
{
echo "Component 1 does A.\n";
$this->mediator->notify($this, "A");
}

public function doB(): void
{
echo "Component 1 does B.\n";
$this->mediator->notify($this, "B");
}
}

class Component2 extends BaseComponent
{
public function doC(): void
{
echo "Component 2 does C.\n";
$this->mediator->notify($this, "C");
}

public function doD(): void
{
echo "Component 2 does D.\n";
$this->mediator->notify($this, "D");
}
}

/**
* The client code.
*/
$c1 = new Component1();
$c2 = new Component2();
$mediator = new ConcreteMediator($c1, $c2);

echo "Client triggers operation A.\n";
$c1->doA();

echo "\n";
echo "Client triggers operation D.\n";
$c2->doD();

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.

Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.

真实世界示例

在本例中, 中介者模式通过提供中心化的事件分发器来对观察者模式进行拓展。 它允许任何对象无需依赖另一个对象所属的类就能跟踪和触发其事件。

index.php: 真实世界示例

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
<?php

namespace RefactoringGuru\Mediator\RealWorld;

/**
* The Event Dispatcher class acts as a Mediator and contains the subscription
* and notification logic. While a classic Mediator often depends on concrete
* component classes, this one is only tied to their abstract interfaces.
*
* We are able to achieve this level of indirection thanks to the way the
* connections between components are established. The components themselves may
* subscribe to specific events that they are interested in via the Mediator's
* subscription interface.
*
* Note, we can't use the PHP's built-in Subject/Observer interfaces here
* because we'll be stretching them too far from what they were designed for.
*/
class EventDispatcher
{
/**
* @var array
*/
private $observers = [];

public function __construct()
{
// The special event group for observers that want to listen to all
// events.
$this->observers["*"] = [];
}

private function initEventGroup(string &$event = "*"): void
{
if (!isset($this->observers[$event])) {
$this->observers[$event] = [];
}
}

private function getEventObservers(string $event = "*"): array
{
$this->initEventGroup($event);
$group = $this->observers[$event];
$all = $this->observers["*"];

return array_merge($group, $all);
}

public function attach(Observer $observer, string $event = "*"): void
{
$this->initEventGroup($event);

$this->observers[$event][] = $observer;
}

public function detach(Observer $observer, string $event = "*"): void
{
foreach ($this->getEventObservers($event) as $key => $s) {
if ($s === $observer) {
unset($this->observers[$event][$key]);
}
}
}

public function trigger(string $event, object $emitter, $data = null): void
{
echo "EventDispatcher: Broadcasting the '$event' event.\n";
foreach ($this->getEventObservers($event) as $observer) {
$observer->update($event, $emitter, $data);
}
}
}

/**
* A simple helper function to provide global access to the event dispatcher.
*/
function events(): EventDispatcher
{
static $eventDispatcher;
if (!$eventDispatcher) {
$eventDispatcher = new EventDispatcher();
}

return $eventDispatcher;
}

/**
* The Observer interface defines how components receive the event
* notifications.
*/
interface Observer
{
public function update(string $event, object $emitter, $data = null);
}

/**
* Unlike our Observer pattern example, this example makes the UserRepository
* act as a regular component that doesn't have any special event-related
* methods. Like any other component, this class relies on the EventDispatcher
* to broadcast its events and listen for the other ones.
*
* @see \RefactoringGuru\Observer\RealWorld\UserRepository
*/
class UserRepository implements Observer
{
/**
* @var array List of application's users.
*/
private $users = [];

/**
* Components can subscribe to events by themselves or by client code.
*/
public function __construct()
{
events()->attach($this, "users:deleted");
}

/**
* Components can decide whether they'd like to process an event using its
* name, emitter or any contextual data passed along with the event.
*/
public function update(string $event, object $emitter, $data = null): void
{
switch ($event) {
case "users:deleted":
if ($emitter === $this) {
return;
}
$this->deleteUser($data, true);
break;
}
}

// These methods represent the business logic of the class.

public function initialize(string $filename): void
{
echo "UserRepository: Loading user records from a file.\n";
// ...
events()->trigger("users:init", $this, $filename);
}

public function createUser(array $data, bool $silent = false): User
{
echo "UserRepository: Creating a user.\n";

$user = new User();
$user->update($data);

$id = bin2hex(openssl_random_pseudo_bytes(16));
$user->update(["id" => $id]);
$this->users[$id] = $user;

if (!$silent) {
events()->trigger("users:created", $this, $user);
}

return $user;
}

public function updateUser(User $user, array $data, bool $silent = false): ?User
{
echo "UserRepository: Updating a user.\n";

$id = $user->attributes["id"];
if (!isset($this->users[$id])) {
return null;
}

$user = $this->users[$id];
$user->update($data);

if (!$silent) {
events()->trigger("users:updated", $this, $user);
}

return $user;
}

public function deleteUser(User $user, bool $silent = false): void
{
echo "UserRepository: Deleting a user.\n";

$id = $user->attributes["id"];
if (!isset($this->users[$id])) {
return;
}

unset($this->users[$id]);

if (!$silent) {
events()->trigger("users:deleted", $this, $user);
}
}
}

/**
* Let's keep the User class trivial since it's not the focus of our example.
*/
class User
{
public $attributes = [];

public function update($data): void
{
$this->attributes = array_merge($this->attributes, $data);
}

/**
* All objects can trigger events.
*/
public function delete(): void
{
echo "User: I can now delete myself without worrying about the repository.\n";
events()->trigger("users:deleted", $this, $this);
}
}

/**
* This Concrete Component logs any events it's subscribed to.
*/
class Logger implements Observer
{
private $filename;

public function __construct($filename)
{
$this->filename = $filename;
if (file_exists($this->filename)) {
unlink($this->filename);
}
}

public function update(string $event, object $emitter, $data = null)
{
$entry = date("Y-m-d H:i:s") . ": '$event' with data '" . json_encode($data) . "'\n";
file_put_contents($this->filename, $entry, FILE_APPEND);

echo "Logger: I've written '$event' entry to the log.\n";
}
}

/**
* This Concrete Component sends initial instructions to new users. The client
* is responsible for attaching this component to a proper user creation event.
*/
class OnboardingNotification implements Observer
{
private $adminEmail;

public function __construct(string $adminEmail)
{
$this->adminEmail = $adminEmail;
}

public function update(string $event, object $emitter, $data = null): void
{
// mail($this->adminEmail,
// "Onboarding required",
// "We have a new user. Here's his info: " .json_encode($data));

echo "OnboardingNotification: The notification has been emailed!\n";
}
}

/**
* The client code.
*/

$repository = new UserRepository();
events()->attach($repository, "facebook:update");

$logger = new Logger(__DIR__ . "/log.txt");
events()->attach($logger, "*");

$onboarding = new OnboardingNotification("1@example.com");
events()->attach($onboarding, "users:created");

// ...

$repository->initialize(__DIR__ . "users.csv");

// ...

$user = $repository->createUser([
"name" => "John Smith",
"email" => "john99@example.com",
]);

// ...

$user->delete();

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
11
UserRepository: Loading user records from a file.
EventDispatcher: Broadcasting the 'users:init' event.
Logger: I've written 'users:init' entry to the log.
UserRepository: Creating a user.
EventDispatcher: Broadcasting the 'users:created' event.
OnboardingNotification: The notification has been emailed!
Logger: I've written 'users:created' entry to the log.
User: I can now delete myself without worrying about the repository.
EventDispatcher: Broadcasting the 'users:deleted' event.
UserRepository: Deleting a user.
Logger: I've written 'users:deleted' entry to the log.

在 Python 中使用模式

复杂度: ★★☆

流行度: ★★☆

使用示例: 中介者模式在 Python 代码中最常用于帮助程序 GUI 组件之间的通信。 在 MVC 模式中, 控制器是中介者的同义词。

概念示例

本例说明了中介者设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

main.py: 概念示例

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
from __future__ import annotations
from abc import ABC


class Mediator(ABC):
"""
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.
"""

def notify(self, sender: object, event: str) -> None:
pass


class ConcreteMediator(Mediator):
def __init__(self, component1: Component1, component2: Component2) -> None:
self._component1 = component1
self._component1.mediator = self
self._component2 = component2
self._component2.mediator = self

def notify(self, sender: object, event: str) -> None:
if event == "A":
print("Mediator reacts on A and triggers following operations:")
self._component2.do_c()
elif event == "D":
print("Mediator reacts on D and triggers following operations:")
self._component1.do_b()
self._component2.do_c()


class BaseComponent:
"""
The Base Component provides the basic functionality of storing a mediator's
instance inside component objects.
"""

def __init__(self, mediator: Mediator = None) -> None:
self._mediator = mediator

@property
def mediator(self) -> Mediator:
return self._mediator

@mediator.setter
def mediator(self, mediator: Mediator) -> None:
self._mediator = mediator


"""
Concrete Components implement various functionality. They don't depend on other
components. They also don't depend on any concrete mediator classes.
"""


class Component1(BaseComponent):
def do_a(self) -> None:
print("Component 1 does A.")
self.mediator.notify(self, "A")

def do_b(self) -> None:
print("Component 1 does B.")
self.mediator.notify(self, "B")


class Component2(BaseComponent):
def do_c(self) -> None:
print("Component 2 does C.")
self.mediator.notify(self, "C")

def do_d(self) -> None:
print("Component 2 does D.")
self.mediator.notify(self, "D")


if __name__ == "__main__":
# The client code.
c1 = Component1()
c2 = Component2()
mediator = ConcreteMediator(c1, c2)

print("Client triggers operation A.")
c1.do_a()

print("\n", end="")

print("Client triggers operation D.")
c2.do_d()

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
11
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.


Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.

在 Ruby 中使用模式

复杂度: ★★☆

流行度: ★★☆

使用示例: 中介者模式在 Ruby 代码中最常用于帮助程序 GUI 组件之间的通信。 在 MVC 模式中, 控制器是中介者的同义词。

概念示例

本例说明了中介者设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

main.rb: 概念示例

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
75
76
77
78
79
80
81
82
83
84
85
86
87
# 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.
class Mediator
# @abstract
#
# @param [Object] sender
# @param [String] event
def notify(_sender, _event)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end

class ConcreteMediator < Mediator
# @param [Component1] component1
# @param [Component2] component2
def initialize(component1, component2)
@component1 = component1
@component1.mediator = self
@component2 = component2
@component2.mediator = self
end

# @param [Object] sender
# @param [String] event
def notify(_sender, event)
if event == 'A'
puts 'Mediator reacts on A and triggers following operations:'
@component2.do_c
elsif event == 'D'
puts 'Mediator reacts on D and triggers following operations:'
@component1.do_b
@component2.do_c
end
end
end

# The Base Component provides the basic functionality of storing a mediator's
# instance inside component objects.
class BaseComponent
# @return [Mediator]
attr_accessor :mediator

# @param [Mediator] mediator
def initialize(mediator = nil)
@mediator = mediator
end
end

# Concrete Components implement various functionality. They don't depend on
# other components. They also don't depend on any concrete mediator classes.
class Component1 < BaseComponent
def do_a
puts 'Component 1 does A.'
@mediator.notify(self, 'A')
end

def do_b
puts 'Component 1 does B.'
@mediator.notify(self, 'B')
end
end

class Component2 < BaseComponent
def do_c
puts 'Component 2 does C.'
@mediator.notify(self, 'C')
end

def do_d
puts 'Component 2 does D.'
@mediator.notify(self, 'D')
end
end

# The client code.
c1 = Component1.new
c2 = Component2.new
ConcreteMediator.new(c1, c2)

puts 'Client triggers operation A.'
c1.do_a

puts "\n"

puts 'Client triggers operation D.'
c2.do_d

output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.

Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.

在 Swift 中使用模式

复杂度: ★★☆

流行度: ★★★

使用示例: 中介者模式在 Swift 代码中最常用于帮助程序 GUI 组件之间的通信。 在 MVC 模式中, 控制器是中介者的同义词。

概念示例

本例说明了中介者设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 Swift 应用案例。

Example.swift: 概念示例

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import XCTest

/// 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.
protocol Mediator: AnyObject {

func notify(sender: BaseComponent, event: String)
}

/// Concrete Mediators implement cooperative behavior by coordinating several
/// components.
class ConcreteMediator: Mediator {

private var component1: Component1
private var component2: Component2

init(_ component1: Component1, _ component2: Component2) {
self.component1 = component1
self.component2 = component2

component1.update(mediator: self)
component2.update(mediator: self)
}

func notify(sender: BaseComponent, event: String) {
if event == "A" {
print("Mediator reacts on A and triggers following operations:")
self.component2.doC()
}
else if (event == "D") {
print("Mediator reacts on D and triggers following operations:")
self.component1.doB()
self.component2.doC()
}
}
}

/// The Base Component provides the basic functionality of storing a mediator's
/// instance inside component objects.
class BaseComponent {

fileprivate weak var mediator: Mediator?

init(mediator: Mediator? = nil) {
self.mediator = mediator
}

func update(mediator: Mediator) {
self.mediator = mediator
}
}

/// Concrete Components implement various functionality. They don't depend on
/// other components. They also don't depend on any concrete mediator classes.
class Component1: BaseComponent {

func doA() {
print("Component 1 does A.")
mediator?.notify(sender: self, event: "A")
}

func doB() {
print("Component 1 does B.\n")
mediator?.notify(sender: self, event: "B")
}
}

class Component2: BaseComponent {

func doC() {
print("Component 2 does C.")
mediator?.notify(sender: self, event: "C")
}

func doD() {
print("Component 2 does D.")
mediator?.notify(sender: self, event: "D")
}
}

/// Let's see how it all works together.
class MediatorConceptual: XCTestCase {

func testMediatorConceptual() {

let component1 = Component1()
let component2 = Component2()

let mediator = ConcreteMediator(component1, component2)
print("Client triggers operation A.")
component1.doA()

print("\nClient triggers operation D.")
component2.doD()

print(mediator)
}
}

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
11
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.

Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.

Component 2 does C.

真实世界示例

Example.swift: 真实世界示例

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import XCTest

class MediatorRealWorld: XCTestCase {

func test() {

let newsArray = [News(id: 1, title: "News1", likesCount: 1),
News(id: 2, title: "News2", likesCount: 2)]

let numberOfGivenLikes = newsArray.reduce(0, { $0 + $1.likesCount })

let mediator = ScreenMediator()

let feedVC = NewsFeedViewController(mediator, newsArray)
let newsDetailVC = NewsDetailViewController(mediator, newsArray.first!)
let profileVC = ProfileViewController(mediator, numberOfGivenLikes)

mediator.update([feedVC, newsDetailVC, profileVC])

feedVC.userLikedAllNews()
feedVC.userDislikedAllNews()
}
}

class NewsFeedViewController: ScreenUpdatable {

private var newsArray: [News]
private weak var mediator: ScreenUpdatable?

init(_ mediator: ScreenUpdatable?, _ newsArray: [News]) {
self.newsArray = newsArray
self.mediator = mediator
}

func likeAdded(to news: News) {

print("News Feed: Received a liked news model with id \(news.id)")

for var item in newsArray {
if item == news {
item.likesCount += 1
}
}
}

func likeRemoved(from news: News) {

print("News Feed: Received a disliked news model with id \(news.id)")

for var item in newsArray {
if item == news {
item.likesCount -= 1
}
}
}

func userLikedAllNews() {
print("\n\nNews Feed: User LIKED all news models")
print("News Feed: I am telling to mediator about it...\n")
newsArray.forEach({ mediator?.likeAdded(to: $0) })
}

func userDislikedAllNews() {
print("\n\nNews Feed: User DISLIKED all news models")
print("News Feed: I am telling to mediator about it...\n")
newsArray.forEach({ mediator?.likeRemoved(from: $0) })
}
}

class NewsDetailViewController: ScreenUpdatable {

private var news: News
private weak var mediator: ScreenUpdatable?

init(_ mediator: ScreenUpdatable?, _ news: News) {
self.news = news
self.mediator = mediator
}

func likeAdded(to news: News) {
print("News Detail: Received a liked news model with id \(news.id)")
if self.news == news {
self.news.likesCount += 1
}
}

func likeRemoved(from news: News) {
print("News Detail: Received a disliked news model with id \(news.id)")
if self.news == news {
self.news.likesCount -= 1
}
}
}

class ProfileViewController: ScreenUpdatable {

private var numberOfGivenLikes: Int
private weak var mediator: ScreenUpdatable?

init(_ mediator: ScreenUpdatable?, _ numberOfGivenLikes: Int) {
self.numberOfGivenLikes = numberOfGivenLikes
self.mediator = mediator
}

func likeAdded(to news: News) {
print("Profile: Received a liked news model with id \(news.id)")
numberOfGivenLikes += 1
}

func likeRemoved(from news: News) {
print("Profile: Received a disliked news model with id \(news.id)")
numberOfGivenLikes -= 1
}
}

protocol ScreenUpdatable: class {

func likeAdded(to news: News)

func likeRemoved(from news: News)
}

class ScreenMediator: ScreenUpdatable {

private var screens: [ScreenUpdatable]?

func update(_ screens: [ScreenUpdatable]) {
self.screens = screens
}

func likeAdded(to news: News) {
print("Screen Mediator: Received a liked news model with id \(news.id)")
screens?.forEach({ $0.likeAdded(to: news) })
}

func likeRemoved(from news: News) {
print("ScreenMediator: Received a disliked news model with id \(news.id)")
screens?.forEach({ $0.likeRemoved(from: news) })
}
}

struct News: Equatable {

let id: Int

let title: String

var likesCount: Int

/// Other properties

static func == (left: News, right: News) -> Bool {
return left.id == right.id
}
}

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
News Feed: User LIKED all news models
News Feed: I am telling to mediator about it...

Screen Mediator: Received a liked news model with id 1
News Feed: Received a liked news model with id 1
News Detail: Received a liked news model with id 1
Profile: Received a liked news model with id 1
Screen Mediator: Received a liked news model with id 2
News Feed: Received a liked news model with id 2
News Detail: Received a liked news model with id 2
Profile: Received a liked news model with id 2


News Feed: User DISLIKED all news models
News Feed: I am telling to mediator about it...

ScreenMediator: Received a disliked news model with id 1
News Feed: Received a disliked news model with id 1
News Detail: Received a disliked news model with id 1
Profile: Received a disliked news model with id 1
ScreenMediator: Received a disliked news model with id 2
News Feed: Received a disliked news model with id 2
News Detail: Received a disliked news model with id 2
Profile: Received a disliked news model with id 2

在 TypeScript 中使用模式

复杂度: ★★☆

流行度: ★★☆

使用示例: 中介者模式在 TypeScript 代码中最常用于帮助程序 GUI 组件之间的通信。 在 MVC 模式中, 控制器是中介者的同义词。

概念示例

本例说明了中介者设计模式的结构并重点回答了下面的问题:

  • 它由哪些类组成?
  • 这些类扮演了哪些角色?
  • 模式中的各个元素会以何种方式相互关联?

index.ts: 概念示例

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
* 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 {
notify(sender: object, event: string): void;
}

/**
* Concrete Mediators implement cooperative behavior by coordinating several
* components.
*/
class ConcreteMediator implements Mediator {
private component1: Component1;

private component2: Component2;

constructor(c1: Component1, c2: Component2) {
this.component1 = c1;
this.component1.setMediator(this);
this.component2 = c2;
this.component2.setMediator(this);
}

public notify(sender: object, event: string): void {
if (event === 'A') {
console.log('Mediator reacts on A and triggers following operations:');
this.component2.doC();
}

if (event === 'D') {
console.log('Mediator reacts on D and triggers following operations:');
this.component1.doB();
this.component2.doC();
}
}
}

/**
* The Base Component provides the basic functionality of storing a mediator's
* instance inside component objects.
*/
class BaseComponent {
protected mediator: Mediator;

constructor(mediator: Mediator = null) {
this.mediator = mediator;
}

public setMediator(mediator: Mediator): void {
this.mediator = mediator;
}
}

/**
* Concrete Components implement various functionality. They don't depend on
* other components. They also don't depend on any concrete mediator classes.
*/
class Component1 extends BaseComponent {
public doA(): void {
console.log('Component 1 does A.');
this.mediator.notify(this, 'A');
}

public doB(): void {
console.log('Component 1 does B.');
this.mediator.notify(this, 'B');
}
}

class Component2 extends BaseComponent {
public doC(): void {
console.log('Component 2 does C.');
this.mediator.notify(this, 'C');
}

public doD(): void {
console.log('Component 2 does D.');
this.mediator.notify(this, 'D');
}
}

/**
* The client code.
*/
const c1 = new Component1();
const c2 = new Component2();
const mediator = new ConcreteMediator(c1, c2);

console.log('Client triggers operation A.');
c1.doA();

console.log('');
console.log('Client triggers operation D.');
c2.doD();

Output.txt: 执行结果

1
2
3
4
5
6
7
8
9
10
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.

Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.

概念示例

中介者模式的一个绝佳例子就是火车站交通系统。 两列火车互相之间从来不会就站台的空闲状态进行通信。 station­Manager车站经理可充当中介者, 让平台仅可由一列入场火车使用, 而将其他火车放入队列中等待。 离场火车会向车站发送通知, 便于队列中的下一列火车进站。

train.go: 组件

1
2
3
4
5
6
7
package main

type train interface {
arrive()
depart()
permitArrival()
}

passengerTrain.go: 具体组件

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
package main

import "fmt"

type passengerTrain struct {
mediator mediator
}

func (g *passengerTrain) arrive() {
if !g.mediator.canArrive(g) {
fmt.Println("PassengerTrain: Arrival blocked, waiting")
return
}
fmt.Println("PassengerTrain: Arrived")
}

func (g *passengerTrain) depart() {
fmt.Println("PassengerTrain: Leaving")
g.mediator.notifyAboutDeparture()
}

func (g *passengerTrain) permitArrival() {
fmt.Println("PassengerTrain: Arrival permitted, arriving")
g.arrive()
}

freightTrain.go: 具体组件

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
package main

import "fmt"

type freightTrain struct {
mediator mediator
}

func (g *freightTrain) arrive() {
if !g.mediator.canArrive(g) {
fmt.Println("FreightTrain: Arrival blocked, waiting")
return
}
fmt.Println("FreightTrain: Arrived")
}

func (g *freightTrain) depart() {
fmt.Println("FreightTrain: Leaving")
g.mediator.notifyAboutDeparture()
}

func (g *freightTrain) permitArrival() {
fmt.Println("FreightTrain: Arrival permitted")
g.arrive()
}

mediator.go: 中介者接口

1
2
3
4
5
6
package main

type mediator interface {
canArrive(train) bool
notifyAboutDeparture()
}

stationManager.go: 具体中介者

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
package main

type stationManager struct {
isPlatformFree bool
trainQueue []train
}

func newStationManger() *stationManager {
return &stationManager{
isPlatformFree: true,
}
}

func (s *stationManager) canArrive(t train) bool {
if s.isPlatformFree {
s.isPlatformFree = false
return true
}
s.trainQueue = append(s.trainQueue, t)
return false
}

func (s *stationManager) notifyAboutDeparture() {
if !s.isPlatformFree {
s.isPlatformFree = true
}
if len(s.trainQueue) > 0 {
firstTrainInQueue := s.trainQueue[0]
s.trainQueue = s.trainQueue[1:]
firstTrainInQueue.permitArrival()
}
}

main.go: 客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

func main() {
stationManager := newStationManger()

passengerTrain := &passengerTrain{
mediator: stationManager,
}
freightTrain := &freightTrain{
mediator: stationManager,
}

passengerTrain.arrive()
freightTrain.arrive()
passengerTrain.depart()
}

output.txt: 执行结果

1
2
3
4
5
PassengerTrain: Arrived
FreightTrain: Arrival blocked, waiting
PassengerTrain: Leaving
FreightTrain: Arrival permitted
FreightTrain: Arrived

根据: Golang By Example