深入浅出设计模式【十七、中介者模式】
一、中介者模式介绍
在复杂的软件系统中,经常存在大量对象之间相互通信和调用的关系。如果每个对象都直接持有并调用其他多个对象的引用,会形成一个网状耦合结构。这种结构会导致:
- 系统难以理解和维护: 对象间的依赖关系错综复杂。
- 可复用性差: 对象因为它与其他对象紧密耦合而无法被单独重用。
- 难以扩展: 任何变更都可能引起“涟漪效应”,需要修改多个类。
中介者模式通过引入一个中介者对象来解决这个问题。该对象负责处理不同对象之间的交互。原本直接相互通信的对象现在只与中介者通信,中介者负责将消息转发到适当的对象。这样,对象间的网状耦合就被转化为了星形耦合,极大地降低了系统的复杂度。
二、核心概念与意图
-
核心概念:
- 中介者 (Mediator): 定义一个接口,用于与各同事对象进行通信。它通常是一个接口或抽象类,声明了同事对象之间交互的方法。
- 具体中介者 (Concrete Mediator): 实现中介者接口,协调各同事对象的行为。它了解并维护所有的同事对象,并负责在它们之间传递消息、协调逻辑和依赖关系。
- 同事类 (Colleague): 定义同事类的抽象父类或接口。它通常持有一个对中介者对象的引用,用于通过中介者与其他同事通信。
- 具体同事类 (Concrete Colleague): 继承或实现同事类。每个具体同事类在需要与其他同事通信时,不是直接与之交互,而是与它的中介者进行通信。
-
意图:
- 用一个中介对象来封装一系列的对象交互。
- 使各对象不需要显式地相互引用,从而使其耦合松散。
- 可以独立地改变对象间的交互,将复杂的交互逻辑集中到中介者中管理。
三、适用场景剖析
中介者模式在以下场景中非常有效:
- 系统中对象之间存在复杂的引用关系: 对象间的依赖关系导致系统结构混乱且难以理解。
- 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类时: 中介者可以充当这个中间类,将原本分散在多个对象中的交互逻辑集中起来。
- 想定制一个分布在多个类中的行为,但又不想在不同类中编写大量代码时: 例如,定义一个复杂的工作流或通信协议,中介者可以成为控制和协调的中心。
- 构建组件化或模块化的系统时: 中介者可以作为模块间的通信枢纽,减少模块间的直接依赖。
不适用场景:
- 如果一组对象已经良好定义、通信简单且稳定,引入中介者只会增加不必要的复杂性。
- 当中介者自身变得过于庞大和复杂时,它可能演变为一个难以维护的“上帝对象”(God Object)。
四、UML 类图解析(Mermaid)
以下UML类图清晰地展示了中介者模式的结构和角色间的关系:
Mediator(中介者接口): 声明一个notify(sender, event)方法。同事对象通过调用此方法来与中介者通信,告知其发生的事件。ConcreteMediator(具体中介者):- 实现
Mediator接口。 - 了解并维护所有需要协调的同事对象的引用 (
-colleagueA,-colleagueB,-colleagueC)。 - 在
notify(sender, event)方法中,包含核心的业务逻辑。它根据发送事件的同事对象 (sender) 和事件类型 (event),来决定需要通知或调用哪个(或哪些)其他同事对象,以及如何调用。这里是所有交互逻辑的集中地。
- 实现
Colleague(同事基类):- 持有对中介者对象的引用 (
-mediator: Mediator)。所有同事对象都通过这个引用来与中介者通信。 - 通常提供一个
onEvent(event)方法,供中介者在需要时调用(即接收中介者的指令)。
- 持有对中介者对象的引用 (
ColleagueA,ColleagueB,ColleagueC(具体同事类):- 继承自
Colleague。 - 实现自己具体的业务方法(如
operationA())。 - 当它们的行为需要影响到其他同事时,不会直接调用其他同事,而是调用
mediator.notify(this, "SomeEvent"),将“球”踢给中介者。
- 继承自
Client(客户端):- 负责创建中介者对象和所有同事对象。
- 负责将中介者设置到每一个同事对象中(通过
colleague.setMediator(mediator))。 - 负责将同事对象注册到中介者中(通常在
ConcreteMediator的构造函数或通过setter方法完成)。
五、各种实现方式及其优缺点
中介者模式的实现关键在于如何设计中介者与同事之间的通信协议。
1. 标准实现(接口 + 事件通知)
即上述UML所描述的方式,同事通过 notify 方法向中介者发送事件。
// 1. Mediator Interface
public interface Mediator {
void notify(Colleague sender, String event);
}
// 2. Concrete Mediator
public class ConcreteMediator implements Mediator {
private ColleagueA colleagueA;
private ColleagueB colleagueB;
private ColleagueC colleagueC;
// Setters for colleagues...
@Override
public void notify(Colleague sender, String event) {
// The core logic: how to react to events from colleagues
if (sender == colleagueA && "eventX".equals(event)) {
System.out.println("Mediator reacts to A's EventX and triggers B and C.");
colleagueB.doSomething();
colleagueC.doSomethingElse();
} else if (sender == colleagueB && "eventY".equals(event)) {
System.out.println("Mediator reacts to B's EventY and triggers A.");
colleagueA.doAnotherThing();
}
// ... handle other events from other colleagues
}
}
// 3. Colleague Base Class
public abstract class Colleague {
protected Mediator mediator;
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
public abstract void onEvent(String event); // To be called by Mediator
}
// 4. Concrete Colleague
public class ColleagueA extends Colleague {
public void operationA() {
System.out.println("ColleagueA does OperationA.");
// When something happens that requires coordination, notify the mediator
mediator.notify(this, "eventX");
}
@Override
public void onEvent(String event) {
System.out.println("ColleagueA handling event from mediator: " + event);
// Do something in response to mediator's instruction
}
}
// 5. Client
public class Client {
public static void main(String[] args) {
ConcreteMediator mediator = new ConcreteMediator();
ColleagueA a = new ColleagueA();
ColleagueB b = new ColleagueB();
ColleagueC c = new ColleagueC();
a.setMediator(mediator);
b.setMediator(mediator);
c.setMediator(mediator);
mediator.setColleagueA(a);
mediator.setColleagueB(b);
mediator.setColleagueC(c);
a.operationA(); // This will trigger the whole chain via the mediator
}
}
- 优点:
- 极大降低了耦合度: 同事类之间完全解耦,它们只依赖于中介者。
- 集中控制交互: 将对象间复杂的交互逻辑集中到中介者中,使得这些交互行为变得清晰且易于维护。
- 简化了同事对象: 同事对象可以变得更容易被复用,因为它们不再包含复杂的交互逻辑。
- 缺点:
- 中介者可能演变为上帝对象: 由于所有交互逻辑都集中在中介者中,它可能变得非常庞大和复杂,难以维护。这是中介者模式最大的风险。
2. 观察者模式与中介者模式的结合
中介者模式天然可以与观察者模式结合。中介者可以被实现为一个事件发布/订阅中心。同事对象不再是调用中介者的 notify 方法,而是发布事件。其他同事对象可以订阅它们关心的事件。中介者负责管理事件订阅和分发。
- 优点:
- 进一步解耦: 同事对象之间完全不知道彼此的存在,甚至不知道中介者的具体逻辑。它们只是发布和订阅事件。
- 更灵活: 可以动态地添加或移除事件的订阅者。
- 缺点:
- 系统行为变得更加间接,跟踪事件流可能会更困难。
六、最佳实践
- 避免“上帝对象”: 这是实施中介者模式时最重要的注意事项。不要将所有业务逻辑都塞进中介者。中介者应该只包含对象间的交互逻辑,而不应包含核心业务逻辑。如果中介者变得过于复杂,考虑:
- 拆分中介者: 根据功能模块创建多个中介者。
- 结合其他模式: 使用状态模式来管理中介者的行为,或使用策略模式来使交互算法可替换。
- 明确中介者的职责: 中介者的核心职责是协调,而不是实现。它应该指挥同事对象去做事,而不是自己亲力亲为。
- 与外观模式 (Facade) 区分:
- 中介者模式: 对象是双向的。同事对象与中介者相互通信、相互影响。目的是协调多个平等对象间的交互。
- 外观模式: 对象是单向的。外观为子系统提供一个简化的接口,客户端只与外观交互。目的是简化接口,隐藏子系统复杂性。
- 优先使用接口:
Mediator和Colleague都应优先定义为接口,以提高灵活性和可测试性。
七、在开发中的演变和应用
中介者模式的思想是现代分布式系统和前端框架架构的核心:
- 消息中间件 (Message Queue / Event Bus): 在微服务架构中,消息队列(如RabbitMQ, Kafka)或事件总线 就是系统级别的中介者。各个微服务(同事)不直接调用其他服务,而是通过向消息队列发布消息或事件来进行异步通信。消息队列负责将消息路由到正确的消费者服务。这完美体现了中介者模式的核心思想。
- 前端框架 (Vue, React, Angular): 在前端MVC/MVVM框架中,组件之间的通信是一个经典问题。父组件与子组件、兄弟组件间的通信如果直接进行,会形成网状耦合。这些框架提供的全局事件总线 (Event Bus) 或 状态管理库(如Vuex, Redux) 就扮演了中介者的角色。组件不再直接相互调用,而是通过中介者(事件总线或Store)来分发动作和状态变更。
- 航空交通管制系统 (Air Traffic Control): 这是一个经典的现实类比。每架飞机(同事)不需要也知道其他所有飞机的飞行计划。它们只需要与塔台(中介者)通信,由塔台来协调所有飞机的起飞、降落和航线,避免碰撞。
八、真实开发案例(Java语言内部、知名开源框架、工具)
-
Java Timer Class (
java.util.Timer):- 从某种角度看,
Timer是一个中介者。你创建多个TimerTask(同事)并将它们调度到同一个Timer上。 Timer对象负责协调这些任务的执行(如单线程顺序执行、处理异常、取消任务),TimerTask之间并不直接交互。Timer知道所有被调度的TimerTask并管理它们的执行逻辑。
- 从某种角度看,
-
Java MVC -
javax.faces.context.FacesContext:- 在JavaServer Faces (JSF) 框架中,
FacesContext对象包含了处理一个特定请求的所有信息。它是一个访问点,不同的JSF组件(如验证器、转换器、渲染器)可以通过它来交互,而不需要直接引用彼此,在一定程度上扮演了中介者的角色。
- 在JavaServer Faces (JSF) 框架中,
-
Spring Framework -
ApplicationEventMulticaster:- Spring的事件发布/订阅机制是中介者模式与观察者模式结合的典范。
- 同事: 任何被Spring管理的Bean,既可以发布事件 (
ApplicationEventPublisher),也可以监听事件 (@EventListener)。 - 中介者:
ApplicationEventMulticaster接口及其实现(如SimpleApplicationEventMulticaster)是真正的中介者。它维护了事件类型与监听器的映射关系。当有事件发布时,多播器负责找到所有监听该事件的Bean,并调用它们的监听方法。 - 这使事件发布者和监听者完全解耦。
-
Swing GUI - Dialog Boxes:
- 在一个包含多个按钮、输入框、选择框的复杂对话框中,这些UI组件之间的交互(例如:勾选某个复选框会禁用另一个输入框)如果直接写死在组件的事件处理器中,会非常混乱。更好的做法是让这些组件的事件处理器都通知一个中介者(对话框本身或一个专门的控制器),由这个中介者来协调所有组件的状态变化。
九、总结
| 方面 | 总结 |
|---|---|
| 模式类型 | 行为型设计模式 |
| 核心意图 | 用一个中介对象来封装一系列对象的交互,使其耦合松散,并可独立地改变交互。 |
| 关键角色 | 中介者(Mediator), 具体中介者(ConcreteMediator), 同事(Colleague), 具体同事(ConcreteColleague) |
| 核心机制 | 1. 星形结构: 所有同事只与中介者通信。 2. 事件通知: 同事通过 notify 告知中介者状态变化。3. 集中协调: 中介者包含所有交互逻辑,指挥同事行动。 |
| 主要优点 | 1. 极大降低对象耦合度,变网状结构为星形结构。 2. 集中控制交互,使交互逻辑清晰易维护。 3. 简化个体对象,使其更易于复用。 |
| 主要缺点 | 1. 中介者可能演变为上帝对象,变得过于复杂,难以维护。这是最大风险。 |
| 适用场景 | 系统中对象间存在复杂的、混乱的引用关系,且交互行为急需集中管理。 |
| 最佳实践 | 严防上帝对象;中介者职责应限于协调;可与观察者模式结合;与外观模式区分。 |
| 现代应用 | 消息队列/事件总线 (微服务通信),前端状态管理 (Vuex, Redux),全局事件机制 (Spring Events)。 |
| 真实案例 | Spring事件多播器 (ApplicationEventMulticaster),消息中间件 (Kafka, RabbitMQ),GUI协调。 |
中介者模式是降低复杂系统耦合度的利器。它将混乱的交互行为秩序化、集中化,是架构师重构和梳理复杂依赖关系的强大工具。然而,其力量也伴随着风险,即可能创造出难以维护的上帝对象。成功应用此模式的关键在于保持中介者自身的简洁和专注,让它做好“协调员”的本职工作,而非“万事通”。在微服务和前端架构中,其思想以消息中间件和状态管理库的形式大放异彩,成为构建大型、松散耦合系统的基石。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120811

浙公网安备 33010602011771号