LOADING

加载过慢请开启缓存 浏览器默认开启

Unity 常用面向对象设计模式系列(12):中介者模式


一、中介者模式概述

意图
用一个中介对象封装一系列对象间的交互,中介者使各同事不需要显式地相互引用,从而使其耦合松散,并可以独立地改变它们之间的交互。

1.1 核心角色

  • Mediator(中介者)
    定义同事间通信接口,负责接收、转发和协调请求。

  • ConcreteMediator(具体中介者)
    实现 Mediator 接口,持有同事引用,封装它们之间的交互逻辑。

  • Colleague(同事接口/抽象类)
    依赖 Mediator,向中介者发送请求,不直接引用其他同事。

  • ConcreteColleague(具体同事)
    实现同事接口,接收来自中介者的通知并执行业务逻辑。


二、UML 类图

MediatorPattern


三、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 中维护递归深度或已通知集合,防止死循环

七、小结

  1. 职责聚焦:中介者只协调,不做业务细节;同事只执行自身逻辑,不做转发;
  2. 接口与 DI:依赖接口编程,将中介者与同事的关系交给依赖注入框架管理;
  3. 动态可配置:结合 ScriptableObject 或配置文件,实现中介者与同事动态注册;
  4. 拆分子中介:对复杂系统,按子系统分层中介者,避免“神对象”升天;
  5. 性能考量:Notify 批量处理可异步或节流,防止瞬时性能抖动。