一、中介者模式概述
意图
用一个中介对象封装一系列对象间的交互,中介者使各同事不需要显式地相互引用,从而使其耦合松散,并可以独立地改变它们之间的交互。
1.1 核心角色
Mediator(中介者)
定义同事间通信接口,负责接收、转发和协调请求。ConcreteMediator(具体中介者)
实现 Mediator 接口,持有同事引用,封装它们之间的交互逻辑。Colleague(同事接口/抽象类)
依赖 Mediator,向中介者发送请求,不直接引用其他同事。ConcreteColleague(具体同事)
实现同事接口,接收来自中介者的通知并执行业务逻辑。
二、UML 类图

三、Unity 实战示例:UI 面板切换中介者
在菜单系统中,我们有两个按钮(“打开设置”“打开商店”)和两个面板(SettingsPanel、ShopPanel),点击按钮时需要隐藏当前面板并显示目标面板。
3.1 定义中介者接口
public interface IUIManager {
/// <summary>
/// 从 sender 发起事件 ev,执行不同面板切换逻辑
/// </summary>
void Notify(object sender, string ev);
}
3.2 具体中介者
public class UIManager : MonoBehaviour, IUIManager {
[SerializeField] private Button settingsButton;
[SerializeField] private Button shopButton;
[SerializeField] private GameObject settingsPanel;
[SerializeField] private GameObject shopPanel;
private ButtonColleague settingsCol;
private ButtonColleague shopCol;
private PanelColleague settingsPanelCol;
private PanelColleague shopPanelCol;
void Awake() {
// 创建同事并注入中介者
settingsCol = new ButtonColleague(settingsButton, this, "Settings");
shopCol = new ButtonColleague(shopButton, this, "Shop");
settingsPanelCol = new PanelColleague(settingsPanel, this, "Settings");
shopPanelCol = new PanelColleague(shopPanel, this, "Shop");
}
public void Notify(object sender, string ev) {
// ev:"Settings" 或 "Shop"
switch (ev) {
case "Settings":
settingsPanelCol.Show();
shopPanelCol.Hide();
break;
case "Shop":
shopPanelCol.Show();
settingsPanelCol.Hide();
break;
}
}
}
3.3 同事抽象与实现
public abstract class Colleague {
protected IUIManager mediator;
protected string id;
protected Colleague(IUIManager med, string id) {
mediator = med;
this.id = id;
}
}
public class ButtonColleague : Colleague {
public ButtonColleague(Button btn, IUIManager med, string id) : base(med, id) {
btn.onClick.AddListener(() => mediator.Notify(this, id));
}
}
public class PanelColleague : Colleague {
private GameObject panel;
public PanelColleague(GameObject panel, IUIManager med, string id) : base(med, id) {
this.panel = panel;
panel.SetActive(false);
}
public void Show() => panel.SetActive(true);
public void Hide() => panel.SetActive(false);
}
- 按钮同事:点击后不直接控制面板,而是向中介者发送通知;
- 面板同事:提供 Show/Hide 方法,由中介者协调调用;
- UIManager:唯一知道所有同事并执行切换逻辑。
四、进阶用法
4.1 动态注册同事
若面板、按钮数量动态变化,可在同事构造时注册到中介者:
public interface IUIManager {
void Register(string id, Colleague colleague);
void Notify(string id);
}
public class UIManager : MonoBehaviour, IUIManager {
private readonly Dictionary<string, Colleague> _map = new();
public void Register(string id, Colleague colleague) {
_map[id] = colleague;
}
public void Notify(string id) {
// 隐藏所有面板
foreach (var kv in _map) {
if (kv.Value is PanelColleague p) p.Hide();
}
// 仅显示目标
if (_map[id] is PanelColleague target) target.Show();
}
}
4.2 跨模块中介者
中介者也可跨 UI、音频、网络等模块,协调复杂流程。例如玩家升级时,通知 UI 更新、播放音效、上传数据。
public interface IGameMediator {
void Notify(string ev, object data = null);
}
public class GameMediator : IGameMediator {
public void Notify(string ev, object data = null) {
switch (ev) {
case "LevelUp":
uiManager.Notify("ShowLevelUp");
audioManager.Play("LevelUpSound");
analyticsManager.TrackEvent("PlayerLevelUp", data);
break;
}
}
}
五、性能与可测试性
- 解耦提升测试:各同事与中介者依赖接口,可 Mock 中介者或同事测试单个组件;
- 避免“巨大中介”:中介者逻辑积累过多容易成为“上帝对象”,应拆分职责或子中介;
- 事件带宽:中介者 Notify 大量同事时可异步或分帧,防止一次性开销过大。
六、注意事项
| 场景 | 陷阱 | 建议 |
|---|---|---|
| “胖中介者” | 所有交互逻辑都堆到中介者中,难以维护 | 将中介者拆成子中介或按模块分层 |
| 同事初始化顺序依赖 | 中介者早于同事注册或反之,导致 Notify 空引用 | 统一由 DI 容器或工厂构建中介者与同事,保证顺序 |
| 缺乏接口抽象 | 同事和中介者直接引用具体类型,导致测试依赖复杂 | 对中介者和同事均使用接口,配合 IOC/Mock 单元测试 |
| 循环通知 | 同事在 Notify 中再次调用中介者,引发循环调用 | 在 Notify 中维护递归深度或已通知集合,防止死循环 |
七、小结
- 职责聚焦:中介者只协调,不做业务细节;同事只执行自身逻辑,不做转发;
- 接口与 DI:依赖接口编程,将中介者与同事的关系交给依赖注入框架管理;
- 动态可配置:结合 ScriptableObject 或配置文件,实现中介者与同事动态注册;
- 拆分子中介:对复杂系统,按子系统分层中介者,避免“神对象”升天;
- 性能考量:Notify 批量处理可异步或节流,防止瞬时性能抖动。