设计模式:中介者模式(Mediator)

中介者模式是把多对象之间存在复杂的网状结构的交互关系,改为 星形结构,这样大大降低对之间的的耦合,所有对象只通过 中介者 与目标对象联系。

中介者例子:人才交流中心,房产中介,WTO(世界贸易组织), MVC框架中的 C(控制器)就是 M(模型)和 V(视图)的中介者。

模式定义

中介者(Mediator)模式:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变他们之间的交互。中介者模式对称为 调停者模式,它是 迪米特法则的典型应用。

模式分析

在中介者模式中,所有成员对象都可以协调工作,但又不直接相互联系。这些对象都与一个处于中心地位的 中介者 对象发生紧密的关系,由这个 中介者 对象进行协调工作。这个协调对象叫做 中介者(Mediator),而中介者所协调的成员对象称做同事(Colleague)对象,这些同事对象通过彼此的相互作用形成系统的行为。

引入中介者对象(Mediator),可以将系统的网状结构变成以中介者为中心的星形结构,同事对象通过中介者与另一个对象进行交互。如果小组成员发生变动,调停者对象可能需要修改,而其它的同事则不必修改。

中介者模式保证了对象结构上的稳定,也就是说,系统的结构不会因为新对象的引入造成大量的修改工作。

面向对象的技术可以更好地协助设计师管理更为复杂的系统。一个好的面向对象的设计可以使对象之间增加协作,减少耦合度。一个深思熟虑的设计会把一个系统分解为一群相互协作的同事对象,然后给每一个同事对象以独特的责任,恰当的配置它们之间的关系,使它们可以在一起工作。

封装首先是行为,以及行为所涉及的状态的封装。行为与状态是不应当分割开来的。中介者模式的用途是管理很多对象的相互作用,以使这些对象可以专注于自身的行为,而独立于其他的对象,让每一个类对自己的行为负责,行为与内部状态不能割裂。

模式优缺点

优点

  1. 可以较少使用静态继承关系,同事类之间各司其职,符合迪米特法则。
  2. 降低了同事对象之间的过度耦合,使得中介者类与同事类可以相对独立扩展。
  3. 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
  4. 中介者模式将对象的行为和协作抽象化,把对象的在小尺度的行为上与其他对象的相互作用分开处理。

缺点

  1. 中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。

中介者模式为同事对象,而不是同事对象提供了可扩展性,所以这个模式所提供的可扩展性是一种(向同事对象)所倾斜的可扩展性。

模式结构

  • 抽象中介者角色:是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。

    在有些情况下,这个抽象对象可以省略。通常由一个Java抽象类或Java对象实现。

  • 具体中介者角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。它从具体同事对象接收消息,转发给其它同事对象。

  • 抽象同事类角色:定义出中介者到同事对象的接口,保存中介者对象,实现所有相互影响的同事类的公共功能。

    同事对象只知道中介者而不知道其他的同事对象。通常由一个Java抽象类或Java对象实现。

  • 具体同事类角色:是抽象同事类的实现者,当需要与其他同事对象交互时,中介者对象负责后续的交互。

结构图与代码

完整版

结构图

中介者模式结构图

示例代码

抽象中介者:定义管理维护同事成员 和 中继转发消息 的方法。

中介者维护同事件的关系,当关系非常多时,中介者可能就会很臃肿。

1
2
3
4
5
6
7
8
9
10
11
/**
* @desc 抽象中介者
*/
public abstract class Mediator {

abstract void register(Colleague colleague);

abstract void relay(Colleague colleague);

abstract void relayToB(Colleague colleague);
}

具本中介者:实现抽象中介者的方法。

在中介者中维护同事间的关联关系,中介者在事件发生时不一定要通知所有的同事对象,而是仅仅会通知有关的同事对象。

开发者需要根据业务逻辑的具体需要决定哪些对象是有关对象,哪些是无关对象。

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
/**
* @desc 具体中介者
*/
public class ConcreteMediator extends Mediator {

protected List<Colleague> colleagueList = new ArrayList<>();

@Override
public void register(Colleague colleague) {
if (!colleagueList.contains(colleague)) {
colleagueList.add(colleague);
}
}

/**
* 广播到所有同事
*
* @param colleague
*/
@Override
public void relay(Colleague colleague) {
for (Colleague c : colleagueList) {
if (!c.equals(colleague)) {
c.receive();
}
}
}

/**
* 转发到指定同事,同事间联系由中介者维护
*
* @param colleague
*/
@Override
void relayToB(Colleague colleague) {
for (Colleague c : colleagueList) {
if (c instanceof ConcreteColleagueB) {
// 这种方式有点向广播,用在群组聊天?
c.receive();
}
}
}
}

抽象同事:需要保存中介者对象,定义接收和发送消息方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @desc 抽象同事类
*/
public abstract class Colleague {

protected Mediator mediator;

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

abstract void receive();

abstract void send();
}

具体同事:执行发送消息和接收消息的业务。具体同事发送状态改变时,通过中介者对象的通知其他同事采用行动。

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
/**
* @desc 具体同事类
*/
public class ConcreteColleagueA extends Colleague {

public ConcreteColleagueA(Mediator mediator) {
super(mediator);
}

@Override
void receive() {
System.out.println("-----> A Receive");
}

@Override
void send() {
System.out.println("-----> A Send");
// mediator.relay(this);
mediator.relayToB(this);
}
}


/**
* @desc 具体同事类
*/
public class ConcreteColleagueB extends Colleague {

public ConcreteColleagueB(Mediator mediator) {
super(mediator);
}

@Override
void receive() {
System.out.println("-----> B Receive");
}

@Override
void send() {
System.out.println("-----> B Send");
mediator.relay(this);
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Client {

public static void main(String[] args) {
// 创建中介者对象
Mediator mediator = new ConcreteMediator();
// 创建同事对象,保存一个中介者对象
Colleague c1 = new ConcreteColleagueA(mediator);
Colleague c2 = new ConcreteColleagueB(mediator);
// 同事对象都注册到中介者对象
mediator.register(c1);
mediator.register(c2);
// 发送消息
c1.send();
System.out.println("--------------------------");
c2.send();
}
}

结果输出

1
2
3
4
5
-----> A Send
-----> B Receive
--------------------------
-----> B Send
-----> A Receive

简化版

在实际开发中,通常采用以下两种方法来简化中介者模式,使开发变得更简单。

  1. 省略中介者接口,把具体中介者对象实现成为单例。
  2. 同事对象不持有中介者,而是在需要的时候直接获取中介者单例对象并调用。

结构图

中介者模式简化版

示例代码

中介者:直接生成单例对象

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
/**
* @desc 具体中介者
*/
public class SimpleMediator {

private static SimpleMediator simpleMediator = new SimpleMediator();

private SimpleMediator() {
}

public static SimpleMediator getInstance() {
return simpleMediator;
}

protected List<SimpleColleague> simpleColleagueList = new ArrayList<>();

public void register(SimpleColleague simpleColleague) {
if (!simpleColleagueList.contains(simpleColleague)) {
simpleColleagueList.add(simpleColleague);
}
}

/**
* 广播到所有同事
*
* @param simpleColleague
*/
public void relay(SimpleColleague simpleColleague) {
for (SimpleColleague c : simpleColleagueList) {
if (!c.equals(simpleColleague)) {
// 这种方式有点向广播
c.receive();
}
}
}

/**
* 转发到指定同事,同事间联系由中介者维护
*
* @param simpleColleague
*/
void relayToB(SimpleColleague simpleColleague) {
for (SimpleColleague c : simpleColleagueList) {
if (c instanceof SimpleConcreteColleagueB) {
// 这种方式有点向广播
c.receive();
}
}
}
}

抽象同事

1
2
3
4
5
6
7
8
9
/**
* @desc 抽象同事类
*/
public abstract class SimpleColleague {

abstract void receive();

abstract void send();
}

具本同事:在创建对象时,获取中介者对象,并将自己注册到中介者对象维护的同事列表中。

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
/**
* @desc 具体同事类
*/
public class SimpleConcreteColleagueA extends SimpleColleague {

// private static SimpleMediator mediator;

public SimpleConcreteColleagueA() {
SimpleMediator mediator = SimpleMediator.getInstance();
mediator.register(this);
}

@Override
void receive() {
System.out.println("-----> A Receive");
}

@Override
void send() {
System.out.println("-----> A Send");
// mediator.relay(this);
SimpleMediator mediator = SimpleMediator.getInstance();
mediator.relayToB(this);
}
}

/**
* @desc 具体同事类
*/
public class SimpleConcreteColleagueB extends SimpleColleague {

// private static SimpleMediator mediator;

public SimpleConcreteColleagueB() {
SimpleMediator mediator = SimpleMediator.getInstance();
mediator.register(this);
}

@Override
void receive() {
System.out.println("-----> B Receive");
}

@Override
void send() {
System.out.println("-----> B Send");
SimpleMediator mediator = SimpleMediator.getInstance();
mediator.relay(this);
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
public class Client {

public static void main(String[] args) {
SimpleColleague simpleColleagueA = new SimpleConcreteColleagueA();
SimpleColleague simpleColleagueB = new SimpleConcreteColleagueB();

simpleColleagueA.send();
System.out.println("--------------------------");
simpleColleagueB.send();
}
}

结果输出

1
2
3
4
5
-----> A Send
-----> B Receive
--------------------------
-----> B Send
-----> A Receive

适用场景

使用 中介者模式,前提一定是能够把责任进行清晰的划分。

责任划分混乱会使系统中的对象与对象之间产生不适当的复杂关系。如果继续这个错误使用 中介者模式 来 化解 这一团乱麻。实际上,责任错误划分的混乱不但得不到改正,而且还会制造一个模名其妙的怪物:一个处于一团乱麻之中的混乱之首。

中介者模式通常在以下场景时使用:

  1. 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解难以复用。
  2. 需要创建一个中间类来封装多个类的行为,而又不想生成太多的子类时。
  3. 交互的公共行为,如果需要改变行为则可以增加新的中介者类。

注意:对模式的研究除了了解应当在什么场景下使用,同时不能忽视这些模式不应当在那些情况下使用。中介者模式是常常被滥用的几个模式之一,且常常在一个糟糕的设计中使用。

应用示例

MVC 架构中控制器

Controller 作为一种中介者,它负责控制视图对象 View 和 模型对象 Model 之间的交互。如在 Spring MVC 中,Controller 就可以作为 JSP 页面与业务对象之间的中介者。

模式扩展

与迪米特法则

迪米特法则:最少知识原则,一个对象应当对其它对象有尽可能少的了解(只与你直接的朋友通信)。

中介者模式就是 迪米特法则 的典型应用,创建一个中介者对象,将系统中有关的对象所引用的其他对象数目减少到最少,使得一个对象与其同事的相互作用被这个对象与中介者对象的相互作用所取代。

与门面模式

  • 门面模式:为子系统提供一个统一简单的对外接口,其消息传递是单方向的。门面模式的客户端只通过门面类向子系统发出消息,不会出现相反的情况。
  • 中介者模式:同事对象间的联系是多方向的。

与观察者模式

中介者模式与观察者模式是功能相似的设计模式,它们之间是相互竞争的关系。即使用其中一者则只能放弃另一者。

  • 观察者模式:引入了观察者对象和主题对象来达到将通信分散化的目的。观察者模式需要观察者对象和主题对象的相互协作才能达到目的,而且一个观察主题通常有几个观察者对象,而一个观察者对象也可以同时观察几个主题对象。
  • 中介者模式:封装对象之间的通信,将通信集中到中介对象中。中介模式不区分施予者与接收者,所有同事对象扮演同样的角色,且过度将事件处理集中化,所以调停者模式不适用于设计一个大型系统的事件处理机制。

典型应用

JDK 中的 Timer 类,查看 Timer 类的结构,会发现 Timer 类中有很多 schedule() 重载方法。

不论是什么样的任务加入到队列中,我们都把这个队列中的对象称为“同事”。查看 sched() 方法的源码可以看出,所有的任务(TimerTask)都放到了 Timer 类维护的 TaskQueue 队列中,同事之间的通信都是通过 Timer 来协调完成的,所以,Timer 承担了中介者的角色,而 TaskQueue 队列内的任务就是具体同事对象。

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
public class Timer {
private final TaskQueue queue = new TaskQueue();
private final TimerThread thread = new TimerThread(queue);

//...........其它省略............

public void schedule(TimerTask task, long delay) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
sched(task, System.currentTimeMillis()+delay, 0);
}

private void sched(TimerTask task, long time, long period) {
if (time < 0)
throw new IllegalArgumentException("Illegal execution time.");

// Constrain value of period sufficiently to prevent numeric
// overflow while still being effectively infinitely large.
if (Math.abs(period) > (Long.MAX_VALUE >> 1))
period >>= 1;

synchronized(queue) {
if (!thread.newTasksMayBeScheduled)
throw new IllegalStateException("Timer already cancelled.");

synchronized(task.lock) {
if (task.state != TimerTask.VIRGIN)
throw new IllegalStateException(
"Task already scheduled or cancelled");
task.nextExecutionTime = time;
task.period = period;
task.state = TimerTask.SCHEDULED;
}
// 维护任务(同事)
queue.add(task);
if (queue.getMin() == task)
// 通信
queue.notify();
}
}
}
作者

光星

发布于

2021-06-21

更新于

2022-06-17

许可协议

评论