中介者模式打造多人聊天室系统
在日常开发中,当多个对象之间存在复杂的交互关系时,直接让对象两两通信会导致代码耦合度极高、维护性极差。就像一个没有主持人的多人聊天室,每个人都要单独和其他人说话,既混乱又容易出错。而中介者模式正是解决这类问题的"金钥匙"------它通过引入一个中介者对象,封装多个对象之间的交互,让对象只需和中介者通信,从而降低耦合、简化逻辑。本文将以《大话设计模式》的通俗风格,手把手教你用C++实现基于中介者模式的多人聊天室系统。
一、先搞懂:中介者模式到底是个啥?
1.1 官方定义(《大话设计模式》版)
中介者模式(Mediator Pattern)的官方定义是:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
这个定义听起来有点抽象,咱们用生活中的例子拆解一下:
- 生活案例1:机场的塔台。飞机之间不会直接沟通"我要起飞""你要降落",而是都和塔台(中介者)通信,塔台统一调度,避免了多架飞机直接交互的混乱;
- 生活案例2:公司的项目经理。开发、测试、产品等岗位(同事类)不需要挨个对接,只需把需求、问题反馈给项目经理(中介者),由项目经理协调各方工作,大大降低了沟通成本。
放到聊天室场景里就更直观了:如果没有聊天室(中介者),用户A要给用户B、C发消息,就得分别找到B和C的联系方式;有了聊天室后,A只需要把消息发给聊天室,由聊天室负责转发给B和C,A完全不用知道B、C的存在,这就是中介者模式的核心价值。
1.2 核心角色(通俗解读)
《大话设计模式》里常说"设计模式是对面向对象设计原则的具体化",中介者模式的核心角色就是对"封装交互、解耦对象"这一原则的落地,主要分为四类:
- 抽象中介者:定义中介者与同事对象交互的接口,就像聊天室的"规则手册",规定了"怎么添加用户""怎么转发消息";
- 具体中介者:实现抽象中介者的接口,是真正干活的"执行者",比如咱们聊天室里负责实际转发消息、管理用户的核心逻辑;
- 抽象同事类:定义同事对象的通用接口,持有中介者的引用,所有参与交互的对象都要遵循这个接口,比如所有聊天用户都得有"发送消息""接收消息"的能力;
- 具体同事类:实现抽象同事类的接口,是具体的"参与者",比如聊天室里的小明、小红、小刚这些真实用户,各自实现接收消息的具体逻辑。
二、UML图:先画骨架再写代码
2.1 中介者模式通用UML图

2.2 聊天室场景UML交互图

三、代码实现:手把手打造聊天室系统
我们按照"先抽象、后具体"的思路,一步步实现聊天室系统,代码完全贴合《大话设计模式》的"易读、易理解"风格,拒绝晦涩难懂的写法。
3.1 第一步:定义抽象角色(抽象中介者+抽象同事类)
cpp
#include <iostream>
#include <vector>
#include <map>
#include <list>
// 抽象中介者:定义聊天室核心接口
class ChatRoomMediator {
public:
// 转发消息接口:由具体中介者实现
virtual void sendMessage(const std::string& sender, const std::string& message) = 0;
// 添加用户接口:将用户纳入中介者管理
virtual void addUser(ChatUser* user) = 0;
// 获取所有用户接口:便于查找发送消息的用户
virtual std::map<std::string, ChatUser*> getUsers() = 0;
// 虚析构:保证子类内存正确释放
virtual ~ChatRoomMediator() = default;
};
// 抽象同事类:定义聊天用户的基础能力
class ChatUser {
private:
std::string name; // 用户名
ChatRoomMediator* mediator; // 持有中介者引用,用户只和中介者交互
std::list<std::string> receivedMessages; // 存储接收的消息
public:
// 构造函数:初始化用户并自动加入聊天室
ChatUser(const std::string& name, ChatRoomMediator* mediator) : name(name), mediator(mediator) {
mediator->addUser(this);
}
// 获取用户名
std::string getName() const {
return name;
}
// 发送消息:委托给中介者,用户不用关心发给谁
void sendMessage(const std::string& message) {
mediator->sendMessage(name, message);
}
// 纯虚函数:接收消息,由具体用户实现
virtual void receiveMessage(const std::string& sender, const std::string& message) = 0;
// 获取接收的消息列表
std::list<std::string> getReceivedMessages() const {
return receivedMessages;
}
protected:
// 辅助函数:添加接收的消息
void addReceivedMessage(const std::string& message) {
receivedMessages.push_back(message);
}
};
3.2 第二步:实现具体角色(具体中介者+具体同事类)
cpp
// 具体同事类:普通聊天用户
class ConcreteChatUser : public ChatUser {
public:
// 构造函数:继承抽象用户的属性和方法
ConcreteChatUser(const std::string& name, ChatRoomMediator* mediator) : ChatUser(name, mediator) {}
// 实现接收消息逻辑:打印消息并保存
void receiveMessage(const std::string& sender, const std::string& message) override {
std::string receivedMessage = getName() + " received: " + sender + "说:" + message;
addReceivedMessage(receivedMessage);
std::cout << receivedMessage << std::endl;
}
};
// 具体中介者:聊天室核心实现
class ChatRoomMediatorImpl : public ChatRoomMediator {
private:
std::map<std::string, ChatUser*> users; // 管理所有加入的用户
public:
// 转发消息:给除发送者外的所有用户发消息
void sendMessage(const std::string& sender, const std::string& message) override {
for (const auto& userPair : users) {
// 不把消息发给发送者自己
if (userPair.first != sender) {
userPair.second->receiveMessage(sender, message);
}
}
}
// 添加用户到聊天室
void addUser(ChatUser* user) override {
users[user->getName()] = user;
}
// 获取所有用户
std::map<std::string, ChatUser*> getUsers() override {
return users;
}
};
3.3 第三步:主函数测试------运行聊天室
cpp
int main() {
std::vector<std::string> userNames;
int N;
std::cout << "请输入聊天室用户数量:";
std::cin >> N;
// 输入用户名
std::cout << "请输入" << N << "个用户名:" << std::endl;
for (int i = 0; i < N; i++) {
std::string userName;
std::cin >> userName;
userNames.push_back(userName);
}
// 创建聊天室(中介者)
ChatRoomMediator* mediator = new ChatRoomMediatorImpl();
// 创建用户并自动加入聊天室
for (const auto& userName : userNames) {
new ConcreteChatUser(userName, mediator);
}
// 发送消息:格式为「用户名 消息内容」,输入非字符串结束
std::cout << "请输入消息(格式:用户名 消息内容),输入任意非字符串结束:" << std::endl;
std::string sender, message;
while (std::cin >> sender >> message) {
ChatUser* user = mediator->getUsers()[sender];
if (user != nullptr) {
user->sendMessage(message);
} else {
std::cout << "用户" << sender << "不存在!" << std::endl;
}
}
// 释放中介者内存
delete mediator;
return 0;
}
四、代码运行演示:看看聊天室效果
4.1 输入示例
请输入聊天室用户数量:3
请输入3个用户名:
小明 小红 小刚
请输入消息(格式:用户名 消息内容),输入任意非字符串结束:
小明 大家晚上好!
小红 小明好~
小刚 都在聊啥?
end
4.2 输出结果
小红 received: 小明说:大家晚上好!
小刚 received: 小明说:大家晚上好!
小明 received: 小红说:小明好~
小刚 received: 小红说:小明好~
小明 received: 小红说:小明好~
小红 received: 小刚说:都在聊啥?
五、深度解析:中介者模式的优缺点与适用场景
5.1 优点(《大话设计模式》划重点)
《大话设计模式》里强调"设计模式的本质是权衡",中介者模式的优点体现在三个核心维度:
- 解耦对象交互:就像聊天室里用户不用知道其他用户的存在,代码中各个对象不再相互引用,新增、删除对象时不会影响其他对象,符合"开闭原则";
- 简化交互逻辑 :所有复杂的交互都集中在中介者中,比如想修改"消息只发给在线用户"的逻辑,只需改中介者的
sendMessage方法,不用改所有用户的代码; - 提高代码可维护性:原本分散在多个对象中的交互逻辑,现在集中在一个中介者里,调试时只需关注中介者,不用在几十个类里找问题。
5.2 缺点
凡事都有两面性,中介者模式也有明显的短板:
- 中介者会"越来越胖":如果系统交互逻辑复杂(比如给聊天室加私聊、禁言、消息撤回功能),中介者类的代码会越来越多,最终变成"大泥球",违反"单一职责原则";
- 单点故障风险:中介者是所有交互的核心,一旦中介者出问题(比如聊天室程序崩溃),整个系统的交互都会瘫痪,就像塔台故障会导致机场所有飞机无法起降。
5.3 适用场景
结合《大话设计模式》的实战经验,以下场景用中介者模式准没错:
- 多个对象之间存在复杂的网状交互,比如GUI界面的按钮、输入框、下拉框等控件的联动(点击按钮后输入框清空、下拉框重置);
- 想避免对象之间的紧耦合,比如电商系统的订单流程(订单、库存、支付、物流不再直接交互,由订单中介者协调);
- 交互逻辑经常变化,比如聊天室需要频繁新增消息类型(文字、图片、语音),只需修改中介者的转发逻辑即可。
总结
中介者模式就像聊天室的"主持人",也像机场的"塔台"、公司的"项目经理"------它把原本杂乱的"一对一"交互,变成了"多对一"的简单交互。本文用《大话设计模式》的通俗风格,结合C++实现了多人聊天室系统,从模式定义、生活案例、UML图到代码实现,完整覆盖了中介者模式的核心知识点。
记住:中介者模式的核心是"封装交互、解耦对象",当你发现代码中多个对象互相引用、逻辑混乱时,不妨试试用中介者模式重构------就像给混乱的聊天室找个主持人,一切都会变得井井有条!