# C\+\+ 中介者模式详解 中介者模式(Mediator Pattern)是**行为型设计模式**之一,核心思想是**用一个中介对象来封装一系列对象的交互逻辑,使各对象不需要显式地相互引用,从而降低耦合,且可以独立地改变它们之间的交互**。它将网状的多对多依赖转化为清晰的一对多星型结构,是迪米特法则(最少知道原则)的典型应用。 ## 一、核心思想与解决的问题 ### 1\. 网状依赖困境 当系统中存在多个相互协作的对象时,如果每个对象都直接持有其他交互对象的引用,会形成复杂的网状依赖结构,带来典型问题: - **耦合爆炸**:一个对象修改接口,所有与之交互的对象都要同步改动; - **扩展困难**:新增一个协作对象,需要修改所有相关对象的交互代码,违反开闭原则; - **逻辑分散**:交互逻辑散落在各个对象中,难以统一维护、调试和复用。 最直观的例子是聊天室:如果每个用户都持有其他所有在线用户的引用,发消息时逐个通知,用户数量增长后依赖关系会指数级膨胀。 ### 2\. 中介者的解法 中介者模式将所有交互逻辑收归中介者统一管理,完成结构转化: - 所有「同事对象」只与中介者通信,不直接持有其他同事的引用; - 中介者负责接收请求,并按业务规则转发给对应的同事,协调多对象的协作逻辑; - 原本的网状依赖转化为星型依赖,同事之间完全解耦,交互逻辑集中在中介者中维护。 ### 3\. 本质 以集中化的方式管理对象间的通信,将多对多的复杂协作关系转化为一对多的受控关系,用中介者的复杂度换取同事对象的简洁、解耦与可扩展性。 ## 二、模式角色与结构 中介者模式包含 4 个核心角色: |角色|作用| |---|---| |**Mediator(抽象中介者)**|声明同事对象通信的统一接口,定义注册、消息转发等核心方法| |**ConcreteMediator(具体中介者)**|实现抽象中介者接口,持有所有同事对象的引用,实现具体的交互协调逻辑| |**Colleague(抽象同事类)**|声明同事的公共接口,内部持有中介者引用,所有跨对象交互都通过中介者完成| |**ConcreteColleague(具体同事类)**|实现抽象同事接口,处理自身业务逻辑,需要交互时向中介者发送请求| ## 三、经典 C\+\+ 实现(继承 \+ 虚函数版) 这是 GoF 定义的标准实现,面向对象风格,通过虚函数实现多态,适合需要动态扩展同事类型的场景。 ### 示例场景 聊天室系统:用户(同事)之间不能直接发消息,所有消息都通过聊天室(中介者)统一转发,中介者可以控制消息过滤、全员广播、定向私聊等规则。 ```cpp #include #include #include #include // 前向声明 class Colleague; // 1. 抽象中介者 class ChatRoomMediator { public: virtual ~ChatRoomMediator() = default; // 注册同事 virtual void registerUser(std::shared_ptr user) = 0; // 转发消息 virtual void sendMessage(const std::string& from, const std::string& to, const std::string& message) = 0; // 全员广播 virtual void broadcast(const std::string& from, const std::string& message) = 0; }; // 2. 抽象同事类 class Colleague { protected: std::shared_ptr mediator; std::string name; public: Colleague(std::string n, std::shared_ptr m) : name(std::move(n)), mediator(std::move(m)) {} virtual ~Colleague() = default; std::string getName() const { return name; } // 发送消息:交给中介者转发 virtual void sendMessage(const std::string& to, const std::string& msg) = 0; virtual void sendBroadcast(const std::string& msg) = 0; // 接收消息 virtual void receiveMessage(const std::string& from, const std::string& msg) = 0; }; // 3. 具体同事类:普通用户 class User : public Colleague { public: using Colleague::Colleague; void sendMessage(const std::string& to, const std::string& msg) override { std::cout << "[" << name << "] 发送私信给 " << to << ": " << msg << std::endl; mediator->sendMessage(name, to, msg); } void sendBroadcast(const std::string& msg) override { std::cout << "[" << name << "] 发送群聊: " << msg << std::endl; mediator->broadcast(name, msg); } void receiveMessage(const std::string& from, const std::string& msg) override { std::cout << "[" << name << "] 收到来自 " << from << " 的消息: " << msg << std::endl; } }; // 4. 具体中介者:聊天室 class ChatRoom : public ChatRoomMediator { private: // 维护所有已注册的用户 std::unordered_map> users; public: void registerUser(std::shared_ptr user) override { users[user->getName()] = std::move(user); } void sendMessage(const std::string& from, const std::string& to, const std::string& message) override { auto it = users.find(to); if (it != users.end()) { it->second->receiveMessage(from, message); } else { std::cout << "[聊天室] 用户 " << to << " 不存在" << std::endl; } } void broadcast(const std::string& from, const std::string& message) override { for (auto& [name, user] : users) { if (name != from) { // 不给自己发 user->receiveMessage(from, message); } } } }; // 客户端使用 int main() { auto chatRoom = std::make_shared(); auto alice = std::make_shared("Alice", chatRoom); auto bob = std::make_shared("Bob", chatRoom); auto charlie = std::make_shared("Charlie", chatRoom); chatRoom->registerUser(alice); chatRoom->registerUser(bob); chatRoom->registerUser(charlie); std::cout << "===== 私信 =====" << std::endl; alice->sendMessage("Bob", "晚上好!"); std::cout << "\n===== 群聊广播 =====" << std::endl; bob->sendBroadcast("大家好,我刚加入聊天室~"); return 0; } ``` ### 输出结果 ```Plain Text ===== 私信 ===== [Alice] 发送私信给 Bob: 晚上好! [Bob] 收到来自 Alice 的消息: 晚上好! ===== 群聊广播 ===== [Bob] 发送群聊: 大家好,我刚加入聊天室~ [Alice] 收到来自 Bob 的消息: 大家好,我刚加入聊天室~ [Charlie] 收到来自 Bob 的消息: 大家好,我刚加入聊天室~ ``` **扩展性验证**: - 新增管理员用户类型,只需新增 `Colleague` 子类,无需修改其他用户代码; - 新增敏感词过滤、消息记录等规则,只需修改中介者,所有用户都自动生效。 ## 四、现代 C\+\+ 实现:回调式中介者 传统继承式中介者灵活性不足,且有虚函数开销。现代 C\+\+ 常结合 `std::function` 实现**事件驱动型中介者**,无需强制继承基类,更轻量灵活,是 GUI、事件系统等场景的主流工程实现。 ### 示例场景 表单控件联动:输入框、提交按钮、复选框三个控件,通过中介者协调状态 —— 勾选「同意协议」后按钮才启用,输入框为空时按钮禁用。 ```cpp #include #include #include #include #include // 事件类型枚举 enum class Event { CheckBoxToggled, InputChanged, ButtonClicked }; // 1. 事件驱动中介者 class FormMediator { private: std::unordered_map>> handlers; public: // 注册事件回调 void on(Event event, std::function handler) { handlers[event].push_back(std::move(handler)); } // 触发事件,通知所有订阅者 void trigger(Event event) { auto it = handlers.find(event); if (it != handlers.end()) { for (auto& handler : it->second) { handler(); } } } }; // 2. 同事类:无需继承基类,只持有中介者指针 class CheckBox { private: FormMediator* mediator; bool checked = false; public: explicit CheckBox(FormMediator* m) : mediator(m) {} void toggle() { checked = !checked; std::cout << "复选框状态: " << (checked ? "勾选" : "取消") << std::endl; mediator->trigger(Event::CheckBoxToggled); } bool isChecked() const { return checked; } }; class InputBox { private: FormMediator* mediator; std::string text; public: explicit InputBox(FormMediator* m) : mediator(m) {} void setText(std::string t) { text = std::move(t); std::cout << "输入框内容: " << text << std::endl; mediator->trigger(Event::InputChanged); } bool isEmpty() const { return text.empty(); } }; class SubmitButton { private: FormMediator* mediator; bool enabled = false; public: explicit SubmitButton(FormMediator* m) : mediator(m) {} void setEnabled(bool e) { enabled = e; std::cout << "提交按钮状态: " << (enabled ? "启用" : "禁用") << std::endl; } void click() { if (enabled) { std::cout << "按钮点击,提交表单" << std::endl; mediator->trigger(Event::ButtonClicked); } else { std::cout << "按钮禁用,无法点击" << std::endl; } } }; // 客户端:在中介者中注册联动逻辑 int main() { FormMediator mediator; CheckBox agreeBox(&mediator); InputBox nameInput(&mediator); SubmitButton submitBtn(&mediator); // 注册联动规则:所有交互逻辑集中在这里 auto updateButtonState = [&]() { bool canSubmit = agreeBox.isChecked() && !nameInput.isEmpty(); submitBtn.setEnabled(canSubmit); }; mediator.on(Event::CheckBoxToggled, updateButtonState); mediator.on(Event::InputChanged, updateButtonState); std::cout << "=== 初始状态 ===" << std::endl; updateButtonState(); std::cout << "\n=== 勾选协议 ===" << std::endl; agreeBox.toggle(); std::cout << "\n=== 输入用户名 ===" << std::endl; nameInput.setText("ZhangSan"); std::cout << "\n=== 点击提交 ===" << std::endl; submitBtn.click(); return 0; } ``` ### 核心优势 - **无继承约束**:同事类不需要继承统一基类,降低了侵入性; - **逻辑集中**:所有联动规则统一在客户端注册到中介者,一目了然; - **零虚函数开销**:基于回调实现,性能更优,适合高频交互场景。 这种事件总线风格的实现,是中介者模式在现代工程中最常用的变体。 ## 五、常见陷阱与注意事项 1. **上帝对象风险** 中介者很容易随着功能迭代不断膨胀,最终变成包含所有业务逻辑的「上帝类」,维护难度剧增。 应对方案:对中介者进行职责拆分,复杂场景下拆分为多个子中介者,各自负责一部分协作逻辑。 2. **过度设计风险** 如果对象间交互非常简单、数量很少,直接通信即可,强行引入中介者反而会增加不必要的间接层和系统复杂度。 3. **性能与链路问题** 所有交互都经过中介者转发,高频交互场景下会有一定的性能损耗;同时调用链路变长,排查问题需要经过中介层,调试成本略有上升。 ## 六、适用场景 1. **复杂网状依赖**:系统中对象间存在复杂的多对多依赖,导致结构混乱难以维护; 2. **交互逻辑集中化**:一组对象的协作逻辑复杂,但可以抽取为公共协调逻辑统一管理; 3. **行为复用与扩展**:需要定制分布在多个类中的协作行为,又不想生成大量子类; 4. **GUI 界面开发**:多个控件之间的联动交互(表单校验、按钮状态联动、页面组件通信); 5. **通信系统**:聊天室、消息总线、游戏匹配系统、航空管制系统、微服务网关等。 ## 七、优缺点分析 ### 优点 1. **解耦性强**:同事对象之间完全解耦,各自独立变化,新增同事类型不影响其他对象; 2. **集中管理**:交互逻辑统一收敛到中介者,便于维护、调试和扩展规则; 3. **符合迪米特法则**:同事只需要知道中介者,不需要了解其他同事的存在和细节; 4. **简化关系结构**:将多对多的复杂通信简化为一对多的星型结构,系统关系更清晰。 ### 缺点 1. **职责易膨胀**:交互逻辑全部集中在中介者,容易演变成上帝对象,复杂度向中介者转移; 2. **单点故障**:中介者出现问题会影响所有协作的同事对象,风险集中; 3. **增加间接层**:所有交互都需要中转,增加了调用链路复杂度和轻微性能开销。 ## 八、与相似模式对比 |模式|类型|核心目的|关键区别| |---|---|---|---| |中介者模式|行为型|封装多个对象间的交互,解耦同事|多对多交互的集中协调,支持双向通信| |观察者模式|行为型|实现发布 \- 订阅,一对多通知|单向通知,主题与观察者可独立扩展,不负责协调逻辑| |外观模式|结构型|简化外部对子系统的访问|单向接口简化,不处理子系统内部的对象交互| |代理模式|结构型|控制对单个对象的访问|一对一代理增强,不涉及多对象的协调| **核心区分**: - 观察者是「一对多的单向通知」,中介者是「多对多的双向协调」; - 外观是对外简化接口,子系统内部可以有耦合;中介者是对内解耦,专门协调内部对象交互。 ## 九、总结 中介者模式的本质是**用一个中间节点收拢所有交互逻辑,将网状依赖转化为星型依赖**,是处理多对象复杂协作场景的核心方案。 - 面向对象设计、需要动态多态扩展同事类型的场景,使用**经典继承式中介者**; - 事件驱动、GUI 交互、轻量业务场景,优先使用**回调 / 事件总线式的现代变体**。 使用的核心判断标准:**当对象间的交互复杂度已经影响到代码可维护性,且交互逻辑可以集中管理时,引入中介者模式**。务必注意控制中介者的职责边界,避免其膨胀为上帝类。