中介者模式:解耦对象交互的优雅之道(C++ 实战与深度解析)

在复杂系统中,对象之间的交互往往像一张错综复杂的网。当一个对象需要与多个其他对象通信时,代码会迅速变得难以维护。中介者模式(Mediator Pattern) 正是为了解决这一问题而生。它通过引入一个“协调者”,将对象间的网状通信转化为星型结构,从而实现松耦合、高内聚的设计。

本文将带你深入理解中介者模式的核心思想,通过完整的 C++ 代码实现,结合真实应用场景,并与观察者模式进行对比,助你在实际项目中游刃有余地应用这一设计模式。


🧩 什么是中介者模式?

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

🎯 核心思想

  • 解耦通信对象:对象不再直接调用彼此的方法。

  • 集中控制逻辑:所有交互逻辑由中介者统一管理。

  • 星型通信结构:对象只与中介者通信,形成“星型”结构。

💡 类比理解: 想象一场会议,如果没有主持人,每个人都要直接与其他所有人沟通,场面混乱。而有了主持人(中介者),所有人只需向主持人发言,由主持人决定谁来回应或执行,会议变得有序高效。


🏗️ 模式结构(UML 简化)

 +----------------+         +------------------+
 |   Colleague    |<--------|    Mediator      |
 +----------------+         +------------------+
        ^                     ^        ^
        |                     |        |
 +----------------+   +--------------+ +--------------+
 |   Concrete     |   | Concrete     | | Concrete     |
 |   ColleagueA   |   | Mediator     | | ColleagueB   |
 +----------------+   +--------------+ +--------------+

✅ 角色详解

角色 职责
Mediator(中介者) 定义同事对象之间交互的接口,通常包含一个 Send 方法用于转发消息。
ConcreteMediator(具体中介者) 实现中介者接口,维护对所有同事对象的引用,并协调它们之间的交互逻辑。
Colleague(同事类) 每个同事类都知道中介者对象,通过它发送和接收消息,不直接与其他同事通信。

✅ C++ 完整实现(现代 C++ 风格)

 #include <iostream>
 #include <string>
 #include <memory>
 #include <vector>
 #include <mutex>

1. 抽象中介者

 // 抽象中介者
 class Mediator {
 public:
     virtual ~Mediator() = default;
     virtual void Send(const std::string& message, const class Colleague* colleague) = 0;
 };

2. 抽象同事类

 // 前向声明
 class Colleague;
 ​
 // 抽象同事类
 class Colleague {
 protected:
     std::weak_ptr<Mediator> mediator;  // 使用 weak_ptr 避免循环引用
     std::string name;
 ​
 public:
     Colleague(const std::shared_ptr<Mediator>& m, const std::string& n)
         : mediator(m), name(n) {}
 ​
     virtual ~Colleague() = default;
 ​
     virtual void Send(const std::string& message) = 0;
     virtual void Receive(const std::string& message) = 0;
 ​
     const std::string& GetName() const { return name; }
 };

3. 具体同事类

 // 具体同事A
 class ConcreteColleagueA : public Colleague {
 public:
     ConcreteColleagueA(const std::shared_ptr<Mediator>& m, const std::string& n)
         : Colleague(m, n) {}
 ​
     void Send(const std::string& message) override {
         std::cout << "[" << name << "] 发送消息: " << message << std::endl;
         
         auto med = mediator.lock();
         if (med) {
             med->Send(message, this);
         }
     }
 ​
     void Receive(const std::string& message) override {
         std::cout << "[" << name << "] 收到消息: " << message << std::endl;
     }
 };
 ​
 // 具体同事B
 class ConcreteColleagueB : public Colleague {
 public:
     ConcreteColleagueB(const std::shared_ptr<Mediator>& m, const std::string& n)
         : Colleague(m, n) {}
 ​
     void Send(const std::string& message) override {
         std::cout << "[" << name << "] 发送消息: " << message << std::endl;
         
         auto med = mediator.lock();
         if (med) {
             med->Send(message, this);
         }
     }
 ​
     void Receive(const std::string& message) override {
         std::cout << "[" << name << "] 收到消息: " << message << std::endl;
     }
 };

4. 具体中介者(支持多同事)

 // 具体中介者
 class ChatRoomMediator : public Mediator {
 private:
     std::vector<std::weak_ptr<Colleague>> colleagues;
     std::mutex mtx; // 线程安全
 ​
 public:
     void AddColleague(const std::shared_ptr<Colleague>& colleague) {
         std::lock_guard<std::mutex> lock(mtx);
         colleagues.push_back(colleague);
     }
 ​
     void Send(const std::string& message, const Colleague* sender) override {
         std::lock_guard<std::mutex> lock(mtx);
         for (auto& weak_colleague : colleagues) {
             auto colleague = weak_colleague.lock();
             if (colleague && colleague.get() != sender) {
                 colleague->Receive(message);
             }
         }
     }
 };

🧪 使用示例:聊天室场景

 int main() {
     // 创建中介者(聊天室)
     auto chatRoom = std::make_shared<ChatRoomMediator>();
 ​
     // 创建用户(同事)
     auto alice = std::make_shared<ConcreteColleagueA>(chatRoom, "Alice");
     auto bob = std::make_shared<ConcreteColleagueB>(chatRoom, "Bob");
     auto charlie = std::make_shared<ConcreteColleagueA>(chatRoom, "Charlie");
 ​
     // 加入聊天室
     chatRoom->AddColleague(alice);
     chatRoom->AddColleague(bob);
     chatRoom->AddColleague(charlie);
 ​
     // 开始聊天
     alice->Send("大家好,我是 Alice!");
     bob->Send("Hi,我是 Bob!");
     charlie->Send("Hello everyone!");
 ​
     return 0;
 }

输出结果:

 [Alice] 发送消息: 大家好,我是 Alice!
 [Bob] 收到消息: 大家好,我是 Alice!
 [Charlie] 收到消息: 大家好,我是 Alice!
 [Bob] 发送消息: Hi,我是 Bob!
 [Alice] 收到消息: Hi,我是 Bob!
 [Charlie] 收到消息: Hi,我是 Bob!
 [Charlie] 发送消息: Hello everyone!
 [Alice] 收到消息: Hello everyone!
 [Bob] 收到消息: Hello everyone!

说明:每个用户只与聊天室(中介者)通信,消息由中介者广播给其他成员,实现了完全解耦。


📊 中介者模式的优点

优点 说明
降低耦合度 各同事对象之间不再直接通信,而是通过中介者,降低了对象之间的耦合。
集中控制交互 所有交互逻辑集中在中介者中,便于维护和扩展。
易于扩展 新增同事类时,只需修改中介者,不影响其他同事。
复用性强 同事类可以独立复用,不依赖其他具体同事。

⚠️ 中介者模式的缺点

缺点 说明
中介者可能过于复杂 所有交互逻辑集中在中介者中,可能导致中介者类变得庞大且难以维护(“上帝类”问题)。
同事类依赖中介者 同事类仍然依赖中介者,只是从“多对多”变成了“一对多”。
性能开销 多了一层间接调用,可能带来轻微性能损失。

🧩 适用场景

  1. GUI 组件通信:窗口中的按钮、文本框、下拉菜单等需要相互通信。

    • 例如:登录窗口中,“记住密码”复选框影响“自动登录”按钮的可用性。

  2. 游戏开发:游戏角色、AI、UI、音效等模块间的协调。

    • 例如:玩家死亡时,UI 显示 Game Over,音效播放悲壮音乐,AI 停止行动。

  3. 微服务架构:服务间通过消息总线通信(如 RabbitMQ、Kafka)。

  4. 航空管制系统:飞机不直接通信,而是通过塔台(中介者)协调。

  5. 编译器设计:词法分析器、语法分析器、语义分析器通过中间代码生成器协调。


🔁 与观察者模式的区别

对比项 中介者模式 观察者模式
目的 封装对象间的交互 定义对象间的一对多依赖
通信方式 双向或单向,由中介者控制 单向,从被观察者到观察者
耦合度 降低对象间直接耦合 解耦观察者与被观察者
典型应用 聊天室、GUI、游戏 事件系统、MVC、发布-订阅

简单记忆

  • 中介者 = “协调员” —— 主动管理交互流程

  • 观察者 = “订阅者” —— 被动接收状态变化通知

🔄 结合使用建议: 在大型系统中,可以将中介者作为事件中心,同事类作为发布者/订阅者,结合两者优势。


✅ 最佳实践建议

  1. 避免中介者变成“上帝类”

    • 如果中介者过于复杂,考虑将其拆分为多个职责单一的中介者(如 UIMediator、GameMediator)。

    • 使用策略模式或命令模式将部分逻辑外移。

  2. 使用智能指针管理生命周期

    • 使用 std::shared_ptrstd::weak_ptr 避免内存泄漏和循环引用。

    • 注意:Colleague 持有 weak_ptr<Mediator>Mediator 持有 weak_ptr<Colleague> 可进一步解耦。

  3. 考虑使用事件总线替代

    • 在大型系统中,可以使用事件总线(Event Bus)或消息队列实现更灵活的通信。

    • 示例:EventBus::getInstance()->publish("player_died", data);

  4. 线程安全

    • 在多线程环境下,确保中介者的操作是线程安全的(如使用 std::mutex)。

    • 可引入无锁队列或异步消息机制提升性能。

  5. 命名规范清晰

    • 推荐命名如 ChatRoomMediatorUIMediator,避免模糊的 ManagerController

  6. 测试友好性

    • 由于交互逻辑集中在中介者,单元测试更集中。

    • 可模拟同事对象测试中介者行为。


🧠 深度思考:中介者 vs 依赖注入 vs 事件驱动

架构风格 特点 适用场景
中介者模式 显式控制交互流程,适合强业务逻辑耦合 GUI、游戏逻辑、有限状态机
依赖注入(DI) 解耦创建与使用,但不解决运行时通信 大型应用组件装配
事件驱动架构 异步、松散耦合,基于消息 分布式系统、高并发服务

融合趋势:现代框架(如 Unreal Engine、Qt)常将三者结合使用,构建高性能、可维护系统。


📝 总结

中介者模式是解决对象间复杂交互的利器。它通过引入一个“协调者”,将对象间的网状通信转化为星型结构,从而实现松耦合、高内聚的设计。

在 C++ 中,结合现代 C++ 特性(如智能指针、lambda、并发支持),我们可以实现更安全、更高效的中介者模式。无论是在 GUI 开发、游戏引擎,还是分布式系统中,中介者模式都能帮助我们构建更清晰、更可维护的代码结构。

记住:设计模式的本质是解耦复用。中介者模式不是消除依赖,而是将依赖从“网状”变为“星状”,从而更好地管理和控制交互逻辑。

🎯 行动建议: 下次当你发现多个类之间频繁调用、难以修改时,不妨思考:是否可以用一个“中介者”来统一协调?也许,这就是架构进化的起点。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐