设计模式之中介者模式

设计模式之中介者模式

意图

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

适用场景

  • 当一些对象和其他对象紧密耦合以致难以对其进行修改时, 可使用中介者模式。
  • 当组件因过于依赖其他组件而无法在不同应用中复用时, 可使用中介者模式。
  • 如果为了能在不同情景下复用一些基本行为, 导致你需要被迫创建大量组件子类时, 可使用中介者模式。

结构

img

结构说明

  1. 组件 (Component) 是各种包含业务逻辑的类。 每个组件都有一个指向中介者的引用, 该引用被声明为中介者接口类型。 组件不知道中介者实际所属的类, 因此你可通过将其连接到不同的中介者以使其能在其他程序中复用。
  2. 中介者 (Mediator) 接口声明了与组件交流的方法, 但通常仅包括一个通知方法。 组件可将任意上下文 (包括自己的对象) 作为该方法的参数, 只有这样接收组件和发送者类之间才不会耦合。
  3. 具体中介者 (Concrete Mediator) 封装了多种组件间的关系。 具体中介者通常会保存所有组件的引用并对其进行管理, 甚至有时会对其生命周期进行管理。
  4. 组件并不知道其他组件的情况。 如果组件内发生了重要事件, 它只能通知中介者。 中介者收到通知后能轻易地确定发送者, 这或许已足以判断接下来需要触发的组件了。
    • 对于组件来说, 中介者看上去完全就是一个黑箱。 发送者不知道最终会由谁来处理自己的请求, 接收者也不知道最初是谁发出了请求。

结构代码范式

Mediator : 为 Colleague 对象定义一个交流接口。

1
2
3
abstract class Mediator {
public abstract void Send(String message, Colleague colleague);
}

ConcreteMediator : 实现 Mediator 中的交流接口。 这个类中需要了解并维护所有的 colleague 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ConcreteMediator extends Mediator {
private ConcreteColleague1 colleague1;
private ConcreteColleague2 colleague2;

public void setColleague1(ConcreteColleague1 colleague1) {
this.colleague1 = colleague1;
}

public void setColleague2(ConcreteColleague2 colleague2) {
this.colleague2 = colleague2;
}

@Override
public void Send(String message, Colleague colleague) {
if (colleague == colleague1) {
colleague2.Notify(message);
} else if (colleague == colleague2){
colleague1.Notify(message);
} else {
System.out.println("Error!");
}
}
}

Colleague 组 : 每个 Colleague 对象应该知道它的 Mediator 对象,但不知道其他同事对象。它只能联系 Mediator 对象。

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
abstract class Colleague {
protected Mediator mediator;

public Colleague(Mediator mediator) {
this.mediator = mediator;
}

public void Send(String message) {
mediator.Send(message, this);
}

public abstract void Notify(String message);
}

class ConcreteColleague1 extends Colleague {
public ConcreteColleague1(Mediator mediator) {
super(mediator);
}

@Override
public void Notify(String message) {
System.out.println("同事1得到信息:" + message);
}
}

class ConcreteColleague2 extends Colleague {
public ConcreteColleague2(Mediator mediator) {
super(mediator);
}

@Override
public void Notify(String message) {
System.out.println("同事2得到信息:" + message);
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MediatorPattern {
public static void main(String[] args) {
ConcreteMediator mediator = new ConcreteMediator();
ConcreteColleague1 colleague1 = new ConcreteColleague1(mediator);
ConcreteColleague2 colleague2 = new ConcreteColleague2(mediator);

mediator.setColleague1(colleague1);
mediator.setColleague2(colleague2);

colleague1.Send("How are you?");
colleague2.Send("Fine, thank you. And you?");
colleague1.Send("I'm fine. Thankes.");
}
}

输出

1
2
3
同事2得到信息:How are you?
同事1得到信息:Fine, thank you. And you?
同事2得到信息:I'm fine. Thankes.

伪代码

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

img

用户触发的元素不会直接与其他元素交流, 即使看上去它们应该这样做。 相反, 元素只需让中介者知晓事件即可, 并能在发出通知时同时传递任何上下文信息。

本例中的中介者是整个认证对话框。 对话框知道具体元素应如何进行合作并促进它们的间接交流。 当接收到事件通知后, 对话框会确定负责处理事件的元素并据此重定向请求。

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")
// ...

与其他模式的关系

  • 责任链模式命令模式中介者模式观察者模式用于处理请求发送者和接收者之间的不同连接方式:
    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 外观模式中介者的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。
    • 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。
    • 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。
  • 中介者观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。
    • 中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。
    • 有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时, 它可能看上去与观察者非常相似。
    • 当你感到疑惑时, 记住可以采用其他方式来实现中介者。 例如, 你可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同, 但这仍是一种中介者模式。
    • 假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象, 而只有一些分布式的观察者。

案例

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

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

参考资料