封面来源:中介者设计模式 (refactoringguru.cn),如有侵权,请联系删除。

参考链接:

0. 前言

工作上的代码不说得心应手,也算是轻松自在。目前来说,较为难处理的是命名,一个直观、准确的命名总是让我纠结万分,抓破脑袋。

前些时候在项目中发现了一个以 Mediator 结尾的类,很显然这个类承担了某些职责。

Mediator 可被翻译为“调解人”,在计算机术语中,常被翻译为“中介者”,与设计模式中的 中介者模式 相关。

1. 模式的定义与特点

当有多个飞行器需要降落在机场时,这些飞行器驾驶员之间并不会直接沟通来决定降落的先后,他们会把自己的情况报告给控制塔台,由塔台指挥飞行器的降落先后。

这里的控制塔台扮演了 中介者 的角色,驾驶员之间不再直接沟通,所有的沟通通过控制塔台完成,如果没有控制塔台,恐怕难以完成一次安全的降落吧。

说到中介,更容易想到租房中介。通过租房中介,租房者与房东之间不直接商讨租房细节,这些细节由中介拟定并告知双方,双方同一后签订租房合同。对于房东而言,租房中介节约了房东的时间;对于租房者而言,租房中介提供了细节完善的合同,降低了租房者的担忧。这样一个“完美的”角色自然是不会白干活,中介会显式(或隐式)地向租房者收取一定的中介费,一般情况下,这份中介费是一个月的房租,因此租房中介会更站向房东,尤其是在房租定价方面,当然,这与本文的关系不大。

在实际项目中类与类之间往往存在复杂的引用依赖关系,为了降低它们之间的耦合关系,这时也需要一个 中介者,这个中介者描述了类与类之间复杂的引用依赖关系,使之不再显式地相互引用,既降低了耦合,也方便后续改变它们之间的引用与依赖。

定义

中介者(Mediator)模式:用一个中介对象来封装对象之间的交互,使对象之间不再显式地相互引用。

中介者模式 又被称为 调停者模式,是一种对象行为型模式。

优点

  • 将多个组件之间的交互抽取到一个中介者类中,符合 单一职责原则,更方便理解与维护;
  • 如果要增加交互规则,只需增加中介者类,无需修改组件类,符合 开闭原则
  • 组件与组件之间的耦合降低,可以更方便地复用它们。

缺点

  • 需要额外引入中介者类,增加了系统的复杂度;

  • 组件之间的复杂交互关系使得中介者类异常复杂,难以维护,并且复杂的中介者可能会降低系统的性能。

2. 适用场景

  • 当组件之间的关系异常紧密,难以对它们进行修改时,可以使用 中介者模式 将这些关系提取到中介者类中;
  • 由于组件之间复杂的依赖关系,难以再次复用这些组件时,可以使用 中介者模式 来描述这些依赖关系,此时这些组件之间不再直接交互,而是通过中介者间接交互,后续复用这些组件变得更加容易;
  • 为了能在不同的场景下复用组件的基本行为,而被迫创建了大量的组件子类,此时可以使用 中介者模式 来抽取组件之间的交互逻辑,后续无需修改组件而是通过增加新的中介者类就能得到新的交互逻辑。

3. 模式的结构

中介者模式中包含 组件中介者 两个角色。

在面向接口编程的思想下,会定义组件和中介者的顶层接口,利用顶层接口的组合与关联完成组件之间的交互逻辑。

中介者模式 的结构图:

中介者模式的结构图

4. 代码的实现

根据上述结构图,很容易完成 中介者模式 的实现。

首先定义顶层组件 Component 和顶层中介者 Mediator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @author mofan
* @date 2024/2/16 22:05
*/
public abstract class Component {
protected Mediator mediator;

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

public abstract void receive();

public abstract void send();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @author mofan
* @date 2024/2/16 22:03
*/
public interface Mediator {
/**
* 注册
*/
void register(Component component);

/**
* 转发
*/
void relay(Component component);
}

然后实现 Component,以 ConcreteComponent1ConcreteComponent2 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author mofan
* @date 2024/2/16 22:13
*/
public class ConcreteComponent1 extends Component {
@Override
public void receive() {
System.out.println("1 号组件收到请求");
}

@Override
public void send() {
System.out.println("1 号组件发出请求,请求转发...");
mediator.relay(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author mofan
* @date 2024/2/16 22:13
*/
public class ConcreteComponent2 extends Component {
@Override
public void receive() {
System.out.println("2 号组件收到请求");
}

@Override
public void send() {
System.out.println("2 号组件发出请求,请求转发...");
mediator.relay(this);
}
}

最后实现 Mediator,定义组件之间的交互与依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @author mofan
* @date 2024/2/16 22:09
*/
public class ConcreteMediator implements Mediator {

private final List<Component> list = new ArrayList<>();

@Override
public void register(Component component) {
if (!list.contains(component)) {
list.add(component);
component.setMediator(this);
}
}

@Override
public void relay(Component component) {
list.stream()
.filter(i -> !i.equals(component))
.forEach(Component::receive);
}
}

在中介者中注册各个具体的组件,当某个组件发出请求时,其他组件会收到请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @author mofan
* @date 2024/2/16 22:24
*/
public class MediatorTest {
@Test
public void test() {
Mediator mediator = new ConcreteMediator();
ConcreteComponent1 component1 = new ConcreteComponent1();
mediator.register(component1);
ConcreteComponent2 component2 = new ConcreteComponent2();
mediator.register(component2);

// 组件 1 号发出请求,其他组件会收到请求
component1.send();
}
}
1 号组件发出请求,请求转发...
2 号组件收到请求

这个例子似乎与 观察者模式 很类似,它俩也的确很类似,但它们各自的目的是有区别的:

  • 中介者模式 是为了消除组件之间的显式依赖关系,这些依赖关系在中介者类中体现;
  • 观察者模式 是为了建立组件之间的 动态 单向连接,使得某个组件的变动能够通知到感兴趣的其他组件,让这些组件对此做出相应。

当中介者对象担任发布者的角色,而其他组件担任订阅者时,此时的 中介者模式观察者模式 很类似,但 中介者模式 的实现并不仅此一种。

5. 模式的拓展

实际开发过程中可以采用以下两种方式对 中介者模式 进行简化:

  1. 不定义顶层中介者接口,将具体中介者对象定义为单例对象;
  2. 具体的组件不再持有中介者对象,而是在需要时直接获取。

此时 中介者模式 的结构图如下:

简化的中介者模式结构图

这种 中介者模式 的实现就难以看到 观察者模式 的影子了。

个人愚见,只要是将类与类之间的依赖关系抽取到一个新类中,都能认为是使用了 中介者模式,至于什么样的依赖关系、新类的内部实现又是怎样的,这些都不重要。

首先定义单例的中介者 SimpleMediator 和组件顶层接口 SimpleComponent

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
/**
* 简化的中介者,不定义接口,直接使用单例对象
*
* @author mofan
* @date 2024/2/16 22:51
*/
public class SimpleMediator {
private static final SimpleMediator MEDIATOR = new SimpleMediator();
private final List<SimpleComponent> components;

private SimpleMediator() {
this.components = new ArrayList<>();
}

public static SimpleMediator getMediator() {
return MEDIATOR;
}

public void register(SimpleComponent component) {
if (!components.contains(component)) {
components.add(component);
}
}

public void relay(SimpleComponent component) {
this.components.stream()
.filter(i -> !i.equals(component))
.forEach(SimpleComponent::receive);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @author mofan
* @date 2024/2/16 22:53
*/
public abstract class SimpleComponent {

public SimpleComponent() {
SimpleMediator.getMediator().register(this);
}

/**
* 接收
*/
public abstract void receive();

/**
* 发送
*/
public abstract void send();
}

然后定义具体的组件 SimpleConcreteComponent1SimpleConcreteComponent2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @author mofan
* @date 2024/2/16 23:00
*/
public class SimpleConcreteComponent1 extends SimpleComponent {

@Override
public void receive() {
System.out.println("1 号简单组件收到请求");
}

@Override
public void send() {
System.out.println("1 号简单组件发出请求,请求转发...");
SimpleMediator.getMediator().relay(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author mofan
* @date 2024/2/16 23:05
*/
public class SimpleConcreteComponent2 extends SimpleComponent {
@Override
public void receive() {
System.out.println("2 号简单组件收到请求");
}

@Override
public void send() {
System.out.println("2 号简单组件发出请求,请求转发...");
SimpleMediator.getMediator().relay(this);
}
}

每实例化一个组件,就会把该组件注册到单例的中介者对象中,无需显式注册。

最终实现的逻辑和先前并无差别,都是当某个组件发出请求时,其他组件就会收到请求:

1
2
3
4
5
6
7
8
9
@Test
public void testSimpleMediator() {
SimpleComponent component1 = new SimpleConcreteComponent1();
// 实例化组件时,就会注册组件到中介者中
new SimpleConcreteComponent2();

// 组件 1 号发送请求,其他组件就会收到请求
component1.send();
}
1 号简单组件发出请求,请求转发...
2 号简单组件收到请求