文章目录
- 一、概述
-
- [1.1 结构与角色](#1.1 结构与角色)
- [1.2 适用场景](#1.2 适用场景)
- 二、实现方式
-
- [2.1 基础实现------聊天室](#2.1 基础实现——聊天室)
- [2.2 使用中介者前后的对比](#2.2 使用中介者前后的对比)
- [2.3 进阶示例------智能家居设备联动](#2.3 进阶示例——智能家居设备联动)
- [三、中介者模式 vs 外观模式](#三、中介者模式 vs 外观模式)
-
- [3.1 核心区别](#3.1 核心区别)
- [3.2 结构对比](#3.2 结构对比)
- [3.3 代码对比](#3.3 代码对比)
- [3.4 选型指南](#3.4 选型指南)
- 四、总结
一、概述
在软件开发中,经常会遇到这样的场景:多个对象之间存在着复杂的网状交互关系------每个对象都需要与其他多个对象通信,导致对象之间互相引用、紧密耦合。例如,聊天系统中每个用户需要持有其他所有用户的引用才能发送消息;GUI 对话框中按钮、文本框、下拉框等组件之间存在联动逻辑,组件间互相引用让代码难以维护。如果让对象之间直接通信,类图会变成一张"蜘蛛网":
对象A
对象B
对象C
对象D
对象E
每个对象都需要维护对其他所有对象的引用,新增一个对象需要修改所有已有对象,这严重违反了开闭原则 和迪米特法则。
中介者模式(Mediator Pattern)正是为了解决这个问题而诞生的------它用一个中介对象来封装一系列对象之间的交互,使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式将"网状结构"变为"星形结构":
对象A
Mediator 中介者
对象B
对象C
对象D
对象E
生活中的中介者模式例子比比皆是:
- 聊天室:用户不会直接给其他用户发消息,而是将消息发送到聊天室(中介者),由聊天室广播给所有在线用户
- 机场塔台:飞机之间不会直接通信协商降落顺序,而是由塔台(中介者)统一调度,每架飞机只需与塔台交互
- 房屋中介:房东和租客不会直接对接,而是通过房产中介(中介者)来传递信息、协调价格、安排看房
- MVC 架构中的 Controller:Model 和 View 不直接交互,Controller 作为中介者协调两者
核心:用一个中介对象来封装一系列对象之间的交互,使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
1.1 结构与角色
中介者模式包含以下角色:
定义通信接口
实现
实现
实现
实现
通知
通知
通知
协调
协调
协调
Mediator 抽象中介者
Colleague 抽象同事类
ConcreteMediator 具体中介者
ConcreteColleagueA 具体同事A
ConcreteColleagueB 具体同事B
ConcreteColleagueC 具体同事C
- Mediator(抽象中介者) :定义同事对象与中介者之间的通信接口,通常包含一个
notify()或send()方法 - ConcreteMediator(具体中介者):实现抽象中介者接口,持有所有同事对象的引用,协调各个同事对象之间的交互关系
- Colleague(抽象同事类):持有中介者的引用,定义同事对象的公共行为
- ConcreteColleague(具体同事类):实现抽象同事类,每个同事对象只与中介者通信,不与其他同事对象直接交互
- 同事对象通过中介者间接通信:同事对象发生状态变化时通知中介者,中介者根据当前状态决定通知哪些其他同事对象做出响应
1.2 适用场景
- 一组对象之间存在复杂的网状通信关系,导致相互依赖且难以理解
- 一个对象需要与多个其他对象通信,但又不想直接持有这些对象的引用
- 想要通过一个中间者来封装和集中管理多个对象之间的交互逻辑
- 一个对象的改变需要联动其他多个对象,但又不知道具体有多少对象需要联动
二、实现方式
中介者模式的核心实现思路是:定义一个中介者对象,持有所有同事对象的引用,同事对象之间不直接通信,而是通过中介者进行间接交互。同事对象只知道自己和中介者,不知道其他同事对象的存在。
以"聊天室"为例,多个用户通过聊天室发送消息,用户之间不直接持有对方的引用,而是由聊天室负责消息的转发:
send(msg)
send(msg)
send(msg)
send(msg)
broadcast(msg)
broadcast(msg)
broadcast(msg)
broadcast(msg)
User-A
ChatRoom 聊天室
User-B
User-C
User-D
2.1 基础实现------聊天室
(1)抽象中介者------聊天室接口
java
/**
* 抽象中介者:聊天室
* 定义用户之间通信的接口
*/
public interface ChatRoom {
/**
* 发送消息
*
* @param user 发送者
* @param message 消息内容
*/
void sendMessage(User user, String message);
/**
* 用户加入聊天室
*
* @param user 用户
*/
void join(User user);
/**
* 用户离开聊天室
*
* @param user 用户
*/
void leave(User user);
}
(2)抽象同事类------用户
java
/**
* 抽象同事类:用户
* 持有中介者的引用,通过中介者与其他用户通信
*/
public abstract class User {
/** 持有中介者引用 */
protected ChatRoom chatRoom;
/** 用户名 */
protected final String name;
public User(String name) {
this.name = name;
}
/**
* 设置聊天室(中介者)
*
* @param chatRoom 聊天室
*/
public void setChatRoom(ChatRoom chatRoom) {
this.chatRoom = chatRoom;
}
/**
* 发送消息
*
* @param message 消息内容
*/
public abstract void send(String message);
/**
* 接收消息
*
* @param from 发送者
* @param message 消息内容
*/
public abstract void receive(String from, String message);
public String getName() {
return name;
}
}
(3)具体中介者------聊天室实现
java
import java.util.ArrayList;
import java.util.List;
/**
* 具体中介者:聊天室实现
* 持有所有用户的引用,负责消息的广播转发
* 用户之间的所有通信都通过聊天室中转
*/
public class ChatRoomImpl implements ChatRoom {
/** 持有所有同事对象的引用 */
private final List<User> users = new ArrayList<>();
@Override
public void join(User user) {
users.add(user);
user.setChatRoom(this);
System.out.println("【系统消息】" + user.getName() + " 加入了聊天室(当前在线 " + users.size() + " 人)");
}
@Override
public void leave(User user) {
users.remove(user);
user.setChatRoom(null);
System.out.println("【系统消息】" + user.getName() + " 离开了聊天室(当前在线 " + users.size() + " 人)");
}
@Override
public void sendMessage(User sender, String message) {
// 中介者决定消息的转发逻辑------广播给所有用户(除了发送者自己)
for (User user : users) {
if (user != sender) {
user.receive(sender.getName(), message);
}
}
}
}
(4)具体同事类------普通用户
java
/**
* 具体同事类:普通用户
* 只与中介者(聊天室)交互,不直接与其他用户通信
*/
public class NormalUser extends User {
public NormalUser(String name) {
super(name);
}
@Override
public void send(String message) {
if (chatRoom == null) {
System.out.println("【错误】" + name + " 未加入任何聊天室,无法发送消息");
return;
}
System.out.println(name + " 发送:" + message);
// 通过中介者转发消息,不直接与接收者交互
chatRoom.sendMessage(this, message);
}
@Override
public void receive(String from, String message) {
System.out.println(name + " 收到来自 " + from + " 的消息:" + message);
}
}
(5)客户端调用
java
public class MediatorDemo {
public static void main(String[] args) {
// 创建中介者------聊天室
ChatRoom chatRoom = new ChatRoomImpl();
// 创建同事对象------用户
User alice = new NormalUser("Alice");
User bob = new NormalUser("Bob");
User charlie = new NormalUser("Charlie");
User diana = new NormalUser("Diana");
// 用户加入聊天室(注册到中介者)
chatRoom.join(alice);
chatRoom.join(bob);
chatRoom.join(charlie);
chatRoom.join(diana);
System.out.println("\n--- 开始聊天 ---");
// 用户发送消息------只与中介者交互,不直接与其他用户通信
alice.send("大家好!");
// Alice 发送:大家好!
// Bob 收到来自 Alice 的消息:大家好!
// Charlie 收到来自 Alice 的消息:大家好!
// Diana 收到来自 Alice 的消息:大家好!
bob.send("你好 Alice!");
// Bob 发送:你好 Alice!
// Alice 收到来自 Bob 的消息:你好 Alice!
// Charlie 收到来自 Bob 的消息:你好 Alice!
// Diana 收到来自 Bob 的消息:你好 Alice!
// 用户离开
chatRoom.leave(diana);
// Diana 离开后,消息不再发送给她
charlie.send("周末有什么计划?");
// Charlie 发送:周末有什么计划?
// Alice 收到来自 Charlie 的消息:周末有什么计划?
// Bob 收到来自 Charlie 的消息:周末有什么计划?
// (Diana 已离开,不会收到)
}
}
关键点 :每个用户只持有
ChatRoom(中介者)的引用,不知道其他用户的存在。新增用户只需加入聊天室即可,无需修改已有用户的代码。消息的转发逻辑集中在中介者中,修改转发规则(如改为私聊、群组)只需修改中介者,同事对象无需改动,符合开闭原则。
2.2 使用中介者前后的对比
如果不使用中介者模式,用户之间直接互相引用------每个用户都要持有其他所有用户的引用:
java
/**
* 不使用中介者模式------用户之间直接耦合
*/
public class UserWithoutMediator {
private final String name;
/** 持有其他所有用户的引用------强耦合 */
private final List<UserWithoutMediator> contacts = new ArrayList<>();
public UserWithoutMediator(String name) {
this.name = name;
}
/**
* 添加联系人------新增一个用户需要通知所有已有用户更新联系人列表
*/
public void addContact(UserWithoutMediator user) {
contacts.add(user);
}
/**
* 发送消息------需要遍历联系人列表逐个发送
*/
public void send(String message) {
System.out.println(name + " 发送:" + message);
for (UserWithoutMediator contact : contacts) {
contact.receive(name, message);
}
}
public void receive(String from, String message) {
System.out.println(name + " 收到来自 " + from + " 的消息:" + message);
}
}
| 对比维度 | 不使用中介者模式 | 使用中介者模式 |
|---|---|---|
| 对象间关系 | 网状结构,每个对象持有多个其他对象的引用 | 星形结构,对象只持有中介者引用 |
| 新增同事对象 | 需要修改所有已有同事对象 | 只需注册到中介者,已有对象无需修改 |
| 通信逻辑 | 分散在各个同事对象中,难以维护 | 集中在中介者中,一处修改全局生效 |
| 耦合度 | 高,同事对象之间相互依赖 | 低,同事对象只与中介者耦合 |
| 可测试性 | 差,需要构造复杂的对象关系网 | 好,可以单独测试同事对象和中介者 |
2.3 进阶示例------智能家居设备联动
以智能家居系统为例,温度传感器、空调、灯光、窗帘之间需要联动------温度传感器检测到温度过高时,空调自动制冷、窗帘自动关闭、灯光调暗。使用中介者模式将这些联动逻辑集中管理:
notify(温度过高)
notify(已关闭)
notify(已关)
notify(已开)
turnOn(cool)
setBrightness(30)
close()
TempSensor 温度传感器
SmartHomeMediator 智能家居中介者
AirConditioner 空调
Light 灯光
Curtain 窗帘
(1)抽象中介者------智能家居中介者
java
/**
* 抽象中介者:智能家居中介者
* 定义设备之间联动协调的接口
*/
public interface SmartHomeMediator {
/**
* 设备状态变化时通知中介者
*
* @param device 触发通知的设备
* @param event 事件类型
*/
void notify(Device device, String event);
}
(2)抽象同事类------设备
java
/**
* 抽象同事类:智能设备
* 持有中介者的引用,通过中介者与其他设备联动
*/
public abstract class Device {
/** 持有中介者引用 */
protected SmartHomeMediator mediator;
/** 设备名称 */
protected final String name;
public Device(String name) {
this.name = name;
}
/**
* 设置中介者
*
* @param mediator 智能家居中介者
*/
public void setMediator(SmartHomeMediator mediator) {
this.mediator = mediator;
}
/**
* 设备状态变化时通知中介者
*
* @param event 事件类型
*/
protected void notifyMediator(String event) {
if (mediator != null) {
mediator.notify(this, event);
}
}
public String getName() {
return name;
}
}
(3)具体中介者------智能家居联动控制器
java
/**
* 具体中介者:智能家居联动控制器
* 持有所有设备的引用,集中管理设备之间的联动逻辑
*/
public class SmartHomeMediatorImpl implements SmartHomeMediator {
private TempSensor tempSensor;
private AirConditioner airConditioner;
private Light light;
private Curtain curtain;
// --- 注册设备 ---
public void setTempSensor(TempSensor tempSensor) {
this.tempSensor = tempSensor;
tempSensor.setMediator(this);
}
public void setAirConditioner(AirConditioner airConditioner) {
this.airConditioner = airConditioner;
airConditioner.setMediator(this);
}
public void setLight(Light light) {
this.light = light;
light.setMediator(this);
}
public void setCurtain(Curtain curtain) {
this.curtain = curtain;
curtain.setMediator(this);
}
@Override
public void notify(Device device, String event) {
// 核心:集中管理所有设备之间的联动逻辑
if (device == tempSensor) {
handleTempSensorEvent(event);
} else if (device == airConditioner) {
handleAirConditionerEvent(event);
}
}
/**
* 处理温度传感器事件
*/
private void handleTempSensorEvent(String event) {
switch (event) {
case "温度过高":
System.out.println("【中介者】检测到温度过高,启动联动降温");
// 联动:关窗帘、开空调制冷、调暗灯光
if (curtain != null) {
curtain.close();
}
if (airConditioner != null) {
airConditioner.startCooling();
}
if (light != null) {
light.setBrightness(30);
}
break;
case "温度正常":
// 温度恢复正常,关闭联动
break;
}
}
/**
* 处理空调事件
*/
private void handleAirConditionerEvent(String event) {
if ("达到目标温度".equals(event)) {
System.out.println("【中介者】空调已达到目标温度,调整联动状态");
if (light != null) {
light.setBrightness(60);
}
}
}
}
(4)具体同事类------温度传感器
java
/**
* 具体同事类:温度传感器
* 检测温度变化,通过中介者触发联动
*/
public class TempSensor extends Device {
public TempSensor(String name) {
super(name);
}
/**
* 温度变化时通知中介者
*
* @param currentTemp 当前温度
*/
public void temperatureChanged(int currentTemp) {
System.out.println(name + ":当前温度 " + currentTemp + "℃");
if (currentTemp > 30) {
notifyMediator("温度过高");
} else if (currentTemp <= 26) {
notifyMediator("温度正常");
}
}
}
(5)具体同事类------空调
java
/**
* 具体同事类:空调
* 只与中介者交互,不与其他设备直接耦合
*/
public class AirConditioner extends Device {
public AirConditioner(String name) {
super(name);
}
/**
* 开始制冷
*/
public void startCooling() {
System.out.println(name + ":启动制冷模式(目标 26℃)");
}
/**
* 关闭
*/
public void turnOff() {
System.out.println(name + ":已关闭");
}
}
(6)具体同事类------灯光、窗帘
java
/**
* 具体同事类:灯光
*/
public class Light extends Device {
public Light(String name) {
super(name);
}
/**
* 设置亮度
*
* @param level 亮度等级(0-100)
*/
public void setBrightness(int level) {
System.out.println(name + ":调节亮度为 " + level + "%");
}
}
/**
* 具体同事类:窗帘
*/
public class Curtain extends Device {
public Curtain(String name) {
super(name);
}
/**
* 关闭窗帘
*/
public void close() {
System.out.println(name + ":窗帘已关闭");
}
/**
* 打开窗帘
*/
public void open() {
System.out.println(name + ":窗帘已打开");
}
}
(7)客户端调用
java
public class SmartHomeMediatorDemo {
public static void main(String[] args) {
// 创建中介者
SmartHomeMediatorImpl mediator = new SmartHomeMediatorImpl();
// 创建设备并注册到中介者
TempSensor sensor = new TempSensor("客厅温度传感器");
AirConditioner ac = new AirConditioner("客厅空调");
Light light = new Light("客厅灯光");
Curtain curtain = new Curtain("客厅窗帘");
mediator.setTempSensor(sensor);
mediator.setAirConditioner(ac);
mediator.setLight(light);
mediator.setCurtain(curtain);
// 模拟温度升高------触发联动
System.out.println("--- 温度升高 ---");
sensor.temperatureChanged(32);
// 客厅温度传感器:当前温度 32℃
// 【中介者】检测到温度过高,启动联动降温
// 客厅窗帘:窗帘已关闭
// 客厅空调:启动制冷模式(目标 26℃)
// 客厅灯光:调节亮度为 30%
// 模拟温度恢复正常
System.out.println("\n--- 温度恢复正常 ---");
sensor.temperatureChanged(25);
// 客厅温度传感器:当前温度 25℃
}
}
关键点 :所有联动逻辑都集中在中介者
SmartHomeMediatorImpl中。新增联动规则(如"温度过高时也打开加湿器")只需在中介者中修改,各个设备类无需任何改动。每个设备只知道自己和中介者,完全解耦。
三、中介者模式 vs 外观模式
中介者模式和外观模式在结构上看起来有些相似------都是引入一个中间对象来封装与其他对象的交互。但它们的意图 、交互方向 和使用场景截然不同。
3.1 核心区别
| 对比维度 | 中介者模式 | 外观模式 |
|---|---|---|
| 核心意图 | 解耦对象之间的网状交互关系,集中管理交互逻辑 | 简化子系统的接口,提供统一的对外入口 |
| 解决的问题 | 对象之间复杂的网状引用关系 | 客户端与子系统之间的复杂依赖 |
| 交互方向 | 双向:同事对象 ↔ 中介者 ↔ 同事对象 | 单向:客户端 → 外观 → 子系统 |
| 对象感知 | 同事对象知道中介者的存在,主动通知中介者 | 子系统不知道外观的存在,被动被调用 |
| 新增行为 | 中介者可以定义新的交互规则 | 外观不新增功能,只组合已有功能 |
| 结构关系 | 中介者是同事对象的枢纽,居中协调 | 外观是子系统的入口,对外封装 |
| 通信方式 | 同事对象主动通知中介者,中介者协调其他同事响应 | 客户端主动调用外观,外观委派给子系统 |
| 典型场景 | 聊天室、塔台调度、GUI 组件联动 | 智能家居一键模式、应用启动引导 |
3.2 结构对比
外观模式
调用
委派
委派
委派
无感知
无感知
Client 客户端
Facade 外观
SubsystemA
SubsystemB
SubsystemC
中介者模式
notify
notify
notify
协调
协调
协调
ColleagueA
Mediator 中介者
ColleagueB
ColleagueC
中介者模式:交互是双向的------同事对象主动通知中介者("我状态变了"),中介者再通知其他同事对象做出响应。同事对象之间互相不知道对方的存在,但都知道中介者的存在。
外观模式:交互是单向的------客户端调用外观,外观委派给子系统,子系统完全不知道外观的存在。子系统之间可能有交互,但外观不参与协调。
3.3 代码对比
以"智能家居"为例,同样的场景下两种模式的使用方式完全不同:
中介者模式的用法------设备之间通过中介者联动:
java
// 中介者模式:设备通过中介者双向通信
public class SmartHomeMediatorImpl implements SmartHomeMediator {
private TempSensor sensor;
private AirConditioner ac;
private Light light;
private Curtain curtain;
@Override
public void notify(Device device, String event) {
// 同事对象主动通知中介者 → 中介者协调其他同事响应
if (device == sensor && "温度过高".equals(event)) {
curtain.close(); // 双向:中介者调用同事方法
ac.startCooling();
light.setBrightness(30);
}
}
}
// 同事对象:知道中介者的存在,主动通知
public class TempSensor extends Device {
public void temperatureChanged(int temp) {
if (temp > 30) {
notifyMediator("温度过高"); // 主动通知中介者
}
}
}
外观模式的用法------用户通过外观一键操作设备:
java
// 外观模式:用户通过外观单向调用设备
public class SmartHomeFacade {
private final Light light = new Light();
private final AirConditioner ac = new AirConditioner();
/**
* 回家模式------外观单向调用设备,设备不知道外观的存在
*/
public void goHome() {
light.on(); // 单向:外观调用子系统
ac.on();
ac.setTemperature(26);
}
}
// 子系统:不知道外观的存在
public class Light {
public void on() {
System.out.println("灯光已打开");
}
}
3.4 选型指南
| 场景 | 选择 |
|---|---|
| 对象之间存在复杂的网状交互,需要集中管理交互逻辑 | 中介者模式 |
| 一对象的状态变化需要联动其他多个对象响应 | 中介者模式 |
| 需要解耦对象之间的相互引用 | 中介者模式 |
| 需要为复杂子系统提供简化的统一对外接口 | 外观模式 |
| 客户端与子系统之间存在强耦合,需要解耦 | 外观模式 |
| 需要按固定流程编排多个子系统的操作顺序 | 外观模式 |
一句话区别 :中介者解决的是"同事之间怎么通信 "(双向协调),外观解决的是"外部怎么使用内部"(单向入口)。
四、总结
中介者模式的核心思想是用一个中介对象来封装一系列对象之间的交互,使各对象不需要显式地相互引用,从而降低耦合度,并使交互逻辑可以独立变化。
优点:
- 降低耦合度:将对象之间的网状交互变为星形结构,每个对象只与中介者耦合
- 集中管理交互逻辑:所有对象的交互规则集中在中介者中,便于维护和修改
- 符合迪米特法则:每个同事对象只需知道中介者,无需了解其他同事对象
- 简化对象间的通信:将多对多的通信变为一对多的通信
- 可独立变化:新增同事对象或修改交互规则,只需修改中介者,同事对象无需改动
缺点:
- 中介者可能臃肿:随着交互逻辑增多,中介者可能变成"上帝对象",承载过多职责
- 同事对象与中介者耦合:同事对象需要知道中介者的存在,存在一定的耦合
- 不适合简单场景:同事对象之间交互简单时,引入中介者反而增加复杂度
- 调试困难:交互逻辑集中后,出现问题难以定位是哪个同事对象引起的
适用场景:
- 一组对象之间存在复杂的网状通信关系,导致相互依赖且难以维护
- 一个对象需要与多个其他对象通信,但又不想直接持有这些对象的引用
- 想要通过一个中间者来集中管理多个对象之间的交互逻辑
- 一个对象的改变需要联动其他多个对象,且联动规则可能频繁变化
中介者 vs 外观 :中介者模式的意图是
解耦对象间交互,同事对象与中介者双向通信,中介者居中协调;外观模式的意图是简化接口,客户端通过外观单向调用子系统,子系统不知道外观的存在。两者虽然都引入了中间对象,但交互方向和意图截然不同。
参考博客:
中介者模式 | 菜鸟教程:https://www.runoob.com/design-pattern/mediator-pattern.html