中介者模式打造多人聊天室系统

中介者模式打造多人聊天室系统

在日常开发中,当多个对象之间存在复杂的交互关系时,直接让对象两两通信会导致代码耦合度极高、维护性极差。就像一个没有主持人的多人聊天室,每个人都要单独和其他人说话,既混乱又容易出错。而中介者模式正是解决这类问题的"金钥匙"------它通过引入一个中介者对象,封装多个对象之间的交互,让对象只需和中介者通信,从而降低耦合、简化逻辑。本文将以《大话设计模式》的通俗风格,手把手教你用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 优点(《大话设计模式》划重点)

《大话设计模式》里强调"设计模式的本质是权衡",中介者模式的优点体现在三个核心维度:

  1. 解耦对象交互:就像聊天室里用户不用知道其他用户的存在,代码中各个对象不再相互引用,新增、删除对象时不会影响其他对象,符合"开闭原则";
  2. 简化交互逻辑 :所有复杂的交互都集中在中介者中,比如想修改"消息只发给在线用户"的逻辑,只需改中介者的sendMessage方法,不用改所有用户的代码;
  3. 提高代码可维护性:原本分散在多个对象中的交互逻辑,现在集中在一个中介者里,调试时只需关注中介者,不用在几十个类里找问题。

5.2 缺点

凡事都有两面性,中介者模式也有明显的短板:

  1. 中介者会"越来越胖":如果系统交互逻辑复杂(比如给聊天室加私聊、禁言、消息撤回功能),中介者类的代码会越来越多,最终变成"大泥球",违反"单一职责原则";
  2. 单点故障风险:中介者是所有交互的核心,一旦中介者出问题(比如聊天室程序崩溃),整个系统的交互都会瘫痪,就像塔台故障会导致机场所有飞机无法起降。

5.3 适用场景

结合《大话设计模式》的实战经验,以下场景用中介者模式准没错:

  • 多个对象之间存在复杂的网状交互,比如GUI界面的按钮、输入框、下拉框等控件的联动(点击按钮后输入框清空、下拉框重置);
  • 想避免对象之间的紧耦合,比如电商系统的订单流程(订单、库存、支付、物流不再直接交互,由订单中介者协调);
  • 交互逻辑经常变化,比如聊天室需要频繁新增消息类型(文字、图片、语音),只需修改中介者的转发逻辑即可。

总结

中介者模式就像聊天室的"主持人",也像机场的"塔台"、公司的"项目经理"------它把原本杂乱的"一对一"交互,变成了"多对一"的简单交互。本文用《大话设计模式》的通俗风格,结合C++实现了多人聊天室系统,从模式定义、生活案例、UML图到代码实现,完整覆盖了中介者模式的核心知识点。

记住:中介者模式的核心是"封装交互、解耦对象",当你发现代码中多个对象互相引用、逻辑混乱时,不妨试试用中介者模式重构------就像给混乱的聊天室找个主持人,一切都会变得井井有条!

相关推荐
Sunsets_Red1 小时前
P8277 [USACO22OPEN] Up Down Subsequence P 题解
c语言·c++·算法·c#·学习方法·洛谷·信息学竞赛
汉克老师1 小时前
GESP2023年12月认证C++二级( 第三部分编程题(2) 小杨的H字矩阵)
c++·算法·矩阵·循环结构·gesp二级·gesp2级
奶茶树2 小时前
【数据结构】红黑树
数据结构·c++·算法
Once_day2 小时前
GCC编译(7)链接脚本LinkerScripts
c语言·c++·编译和链接·程序员自我修养
问好眼2 小时前
《算法竞赛进阶指南》0x01 位运算-2.增加模数
c++·算法·位运算·信息学奥赛
小米4962 小时前
js设计模式 --- 工厂模式
设计模式
LYS_06182 小时前
C++学习(8)(文件输入输出,类和对象(1))
开发语言·c++·学习
历程里程碑2 小时前
26信号处理一:从闹钟到进程控制的奥秘
linux·运维·服务器·开发语言·c++·算法·排序算法
载数而行5202 小时前
算法系列4之插入排序
数据结构·c++·算法·排序算法