精读C++20设计模式:行为型设计模式:中介者模式
精读C++20设计模式:行为型设计模式:中介者模式
前言
中介者模式试图做的事情很简单。他跟其他行为型设计模式类似,都希望交互对象的耦合是松散的而不是紧密的。对于特别复杂的系统里,对象之间的交互会像蛛网一样纠结:A 直接调用 B,B 又调用 C,C 反过来修改 A 的状态——改动一处,波及多处。
中介者模式的核心思想很简单也很优雅:把对象之间的交互搬到一个单独的“中介者”上,让对象只与中介者通信。系统由“网状耦合”变成“星形耦合”,把路由/协议/协调逻辑集中起来,降低对象之间的直接依赖。
什么是中介者模式(简短定义)
中介者模式定义了一个中介对象来封装一组对象之间的交互。中介者使各同事对象不需要显式地相互引用,从而使其耦合松散,并且可以独立地改变它们之间的交互。大家的交互更加松散起来了。每个人的接口可能都只知道一个中介者的接口,大家利用中介者交谈,中介者负责路由、规则、调度或转换消息。
示例 1 — 聊天室 Demo(最经典的例子)
聊天室是大家都写过的。这里写一个超级简单迷你的版本用户(Colleague)通过 ChatRoom
(Mediator)发送消息,ChatRoom
决定如何分发。
// chatroom.cpp — C++20 示例(单文件)
#include <iostream>
#include <string>
#include <memory>
#include <unordered_map>
#include <vector>struct IMediator {virtual ~IMediator() = default;virtual void send_message(const std::string& from, const std::string& to, const std::string& msg) = 0;virtual void broadcast(const std::string& from, const std::string& msg) = 0;
};class User {
public:User(std::string name, IMediator* mediator) : name_(std::move(name)), mediator_(mediator) {}const std::string& name() const { return name_; }void send_to(const std::string& to, const std::string& msg) {mediator_->send_message(name_, to, msg);}void send_broadcast(const std::string& msg) {mediator_->broadcast(name_, msg);}void receive(const std::string& from, const std::string& msg) {std::cout << "[" << name_ << "] 收到来自 " << from << " 的消息: " << msg << "\n";}
private:std::string name_;IMediator* mediator_;
};class ChatRoom : public IMediator {
public:void register_user(std::shared_ptr<User> user) {users_[user->name()] = user;}void send_message(const std::string& from, const std::string& to, const std::string& msg) override {auto it = users_.find(to);if (it != users_.end()) {it->second->receive(from, msg);} else {// 可以把未找到的处理策略放在中介者中std::cout << "用户 " << to << " 不在线或不存在(由中介者处理)\n";}}void broadcast(const std::string& from, const std::string& msg) override {for (auto& [name, user] : users_) {if (name != from) user->receive(from, msg);}}
private:std::unordered_map<std::string, std::shared_ptr<User>> users_;
};// 使用示例
int main() {ChatRoom room;auto alice = std::make_shared<User>("Alice", &room);auto bob = std::make_shared<User>("Bob", &room);auto carol = std::make_shared<User>("Carol", &room);room.register_user(alice);room.register_user(bob);room.register_user(carol);alice->send_to("Bob", "hi Bob!");bob->send_broadcast("大家好,我是 Bob");carol->send_to("Dave", "你在哪?"); // Dave 不在线return 0;
}
你马上注意到,我们压根就没有出现类似:User::sendMessageTo(const User&)
的签名,好像我们搞错了什么一样。相反,我们一直告诉中介者我们要给谁——User跟User的交互从复杂的网状转向了星型的结构(我们甚至可以从完全的无序网状到经典的单一中心再到分布式,但是User到User的逻辑功能丝毫不会收到任何影响)。更重要的是——任何聊天的逻辑(如是否允许私聊、是否保存日志、是否过滤敏感词等)都从User这里给剥离出去了,
- 如果需要持久化、审计、权限检查,只需在中介者里增加对应逻辑,不必改动
User
。
示例 2 — 中介者 + 事件(Event-driven Mediator)
当系统更复杂时,中介者也可以作为“事件总线 / 事件路由器”来使用。事件驱动中介者将“事件类型”与“订阅者回调”解耦,支持更灵活的编排。
// event_mediator.cpp — 简化版事件中介者
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <functional>class EventMediator {
public:using Handler = std::function<void(const std::string& payload)>;// 订阅某类事件(event_name)void subscribe(const std::string& event_name, Handler h) {handlers_[event_name].push_back(std::move(h));}// 发布事件,按订阅者回调投递void publish(const std::string& event_name, const std::string& payload) {auto it = handlers_.find(event_name);if (it != handlers_.end()) {for (auto &h : it->second) {h(payload);}}}
private:std::unordered_map<std::string, std::vector<Handler>> handlers_;
};// 使用示例
int main() {EventMediator mediator;mediator.subscribe("chat.message", [](const std::string& p){std::cout << "[logger] 保存消息: " << p << "\n";});mediator.subscribe("chat.message", [](const std::string& p){std::cout << "[analytics] 增加消息计数: " << p << "\n";});mediator.publish("chat.message", "Alice -> Hello world");mediator.publish("user.login", "Bob 上线"); // 无订阅时安全忽略return 0;
}
事件通知就是一个经典的中介设计——事件驱动中心让各个组件的通信成为了事件的通知而不是直接的调用。
- 事件中介者把“谁关心什么事件”作为中介者的职责,发布者和订阅者只需关心事件语义。
- 这是构建松耦合微服务、模块间通信或者插件系统的常用做法(例如:UI 组件发出事件、由中介者分发给需要的服务)。
示例 3 — 中介者的“服务热线”(智能路由 / 负载分配)
把中介者想成“调度员”会很直观:例如客服系统里,用户来电由中介者(路由器)分配到空闲或最适合的客服代表(agent)。把决策逻辑集中到中介者中,可以灵活策略化(轮询、优先级、技能匹配、加权等)。
// hotline_mediator.cpp — 简化热线调度
#include <iostream>
#include <string>
#include <vector>
#include <optional>
#include <functional>struct Agent {std::string name;bool available = true;std::vector<std::string> skills; // 如 { "billing", "technical" }void handle(const std::string& user, const std::string& issue) {available = false;std::cout << "[" << name << "] 处理用户 " << user << " 的问题: " << issue << "\n";// 处理结束后设为可用(示例中直接释放)available = true;}
};class HotlineMediator {
public:void add_agent(Agent a) { agents_.push_back(std::move(a)); }// 简单策略:按技能优先匹配,再轮询就近分配void route_call(const std::string& user, const std::string& issue, const std::string& required_skill="") {// 1. 优先找能处理该技能且可用的for (auto &ag : agents_) {if (ag.available && (required_skill.empty() || has_skill(ag, required_skill))) {ag.handle(user, issue);return;}}// 2. 找任何可用的for (auto &ag : agents_) {if (ag.available) {ag.handle(user, issue);return;}}// 3. 全部忙,放入队列或提示等待(中介者负责排队)std::cout << "所有客服均忙,请稍后重试或排队(由中介者在此处实现队列逻辑)\n";}
private:bool has_skill(const Agent& a, const std::string& s) {for (auto &k : a.skills) if (k == s) return true;return false;}std::vector<Agent> agents_;
};// 使用示例
int main() {HotlineMediator mediator;mediator.add_agent(Agent{"Alice", true, {"billing", "general"}});mediator.add_agent(Agent{"Bob", true, {"technical"}});mediator.add_agent(Agent{"Carol", false, {"general"}}); // 暂不可用mediator.route_call("用户001", "无法支付,扣款失败", "billing");mediator.route_call("用户002", "APP 崩溃", "technical");mediator.route_call("用户003", "想了解套餐"); // 没指定技能 -> 任意可用return 0;
}
Hotline 中介者把“谁来接、按什么规则接”放在一处。以后如果想改策略(按技能匹配权重、加入优先级、按SLA分配、或把请求发给外包),只需修改中介者即可,不必更改 Agent 或 Caller 的实现。
总结
我们试图解决什么问题?
- 对象之间耦合过高:每个对象都持有其他对象的引用并直接调用,导致改动传播、测试困难。
- 交互逻辑散落在多个类中:路由、过滤、权限、日志、转换等逻辑散落在同事对象,使得职责不清晰。
- 难以统一变更/策略化:当交互规则变更(例如需要增加日志或限流)时,必须在多个地方修改代码。
- 复杂的协调/调度需求:例如多方协商、优先级处理、并发控制等,逐一放在每个对象会非常混乱。
我们如何解决问题?
- 引入中介者(Mediator)对象:所有需要交互的同事对象不再直接互相调用,而是通过中介者发送/接收消息或事件。
- 集中化交互逻辑:中介者负责路由决策、数据转换、权限校验、队列/重试、审计日志等。
- 支持多种风格:中介者既可以是简单的“发-收”桥(如聊天室),也可以是事件总线(publish/subscribe),或是智能调度器(热线)。
- 策略化与扩展:中介者内部的调度/路由策略可以抽象成策略对象或插件,使得行为可配置、可替换。
优点与缺点分析
优点
- 降低类之间的耦合度:同事对象只依赖中介者接口,易于替换与测试。
- 集中管理复杂逻辑:路由、权限、审计等逻辑在一个地方实现,维护性更好。
- 更方便演进与扩展:新规则、新功能(如限流、备份路由)只需要改中介者或新增中介者策略。
- 便于实现跨模块的横切功能:如记录、监控、异常处理等可在中介者层统一处理。
缺点(以及如何缓解)
- 中介者可能变成“上帝对象”:把太多逻辑塞进去会导致中介者复杂臃肿。
- 缓解:把中介者内部拆成多个策略/处理链(Chain of Responsibility),或拆出多个中介者(按领域划分)。
- 增加单点责任:中介者成为交互路径的关键,若中介者出问题会影响整个系统。
- 缓解:中介者自身要设计成高可用、可替换(接口化、支持备份/降级)。
- 性能开销:中介者增加了一层转发,极端低延迟场景可能需权衡。
- 缓解:在高性能场景可以采用直接调用 + 中介者用于非关键路径,或者让中介者做并发优化(批量转发、零拷贝等)。