中介者模式:模式动机与定义

先聊个我早年踩过的坑。

那时候我在做一个即时通讯的后台模块。每个用户对象都要跟其他用户对象打交道——发消息、拉黑、加好友、状态同步。你想想看,如果每个对象都直接持有其他对象的引用,代码会变成什么样?

嗯,我当时的代码里,一个User类里塞了十几个指针,指向其他User。每次新增一个交互行为,就得改好几个类的接口。更别提调试了——对象之间的调用链绕来绕去,跟一团乱麻似的。

后来我重构了那套代码,引入了中介者模式。说白了,就是让所有对象不再直接通信,而是通过一个中介者来转发消息。这个中介者负责协调各个对象之间的交互逻辑。

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

这个模式的核心思想其实很简单:多对多的通信,转变成一对多的通信。所有同事对象只跟中介者打交道,中介者知道所有同事对象,但同事对象之间互不知晓。

角色与结构

中介者模式涉及以下几个角色:

角色 名称 职责
Mediator 抽象中介者 定义通信接口,声明同事对象之间的交互方法
ConcreteMediator 具体中介者 实现中介者接口,协调各个同事对象的交互逻辑
Colleague 抽象同事类 持有中介者的引用,定义自身的业务方法
ConcreteColleague 具体同事类 实现自身的业务逻辑,通过中介者与其他同事通信

我画了一张结构图,你看一眼就明白了:

ConcreteMediator ConcreteColleague1 ConcreteColleague2 ConcreteColleague3 所有同事对象只与中介者通信 同事之间没有直接依赖关系 --- 虚线箭头:通信方向 ✕ 红色叉号:禁止直接通信

你看这个图,同事1、同事2、同事3之间没有任何直接连线。它们要通信,都得经过中间那个中介者。这就是中介者模式的核心结构。

C++代码实现

咱们直接上代码。我写一个聊天室的例子,这样好理解。

// 前置声明
class Colleague;

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

// 抽象同事类
class Colleague {
protected:
    Mediator* mediator_;
    std::string name_;
public:
    Colleague(const std::string& name, Mediator* mediator)
        : name_(name), mediator_(mediator) {}
    
    virtual ~Colleague() = default;
    
    virtual void send(const std::string& msg) {
        mediator_->sendMessage(msg, this);
    }
    
    virtual void receive(const std::string& msg) {
        std::cout << "[" << name_ << "] 收到消息: " << msg << std::endl;
    }
    
    std::string getName() const { return name_; }
};

// 具体同事类:普通用户
class User : public Colleague {
public:
    using Colleague::Colleague;
    
    void receive(const std::string& msg) override {
        std::cout << "[用户 " << getName() << "] " << msg << std::endl;
    }
};

// 具体同事类:管理员
class Admin : public Colleague {
public:
    using Colleague::Colleague;
    
    void receive(const std::string& msg) override {
        std::cout << "[管理员 " << getName() << "] " 
                  << "[系统通知] " << msg << std::endl;
    }
};

// 具体中介者:聊天室
class ChatRoom : public Mediator {
private:
    std::vector<Colleague*> colleagues_;
    
public:
    void addColleague(Colleague* colleague) override {
        colleagues_.push_back(colleague);
        // 通知其他用户有新成员加入
        std::string joinMsg = colleague->getName() + " 加入了聊天室";
        for (auto* c : colleagues_) {
            if (c != colleague) {
                c->receive(joinMsg);
            }
        }
    }
    
    void sendMessage(const std::string& msg, Colleague* sender) override {
        for (auto* c : colleagues_) {
            // 不把消息发回给发送者自己
            if (c != sender) {
                c->receive(sender->getName() + ": " + msg);
            }
        }
    }
};

个人经验:我在实际项目中,经常把中介者设计成单例。因为一个系统里通常只需要一个协调中心。但要注意,如果中介者变得过于庞大,就要考虑拆分了——我见过一个中介者里塞了上百个方法的代码,那比不用中介者还糟糕。

对象间通信

咱们来看看怎么用这个聊天室:

int main() {
    // 创建中介者
    ChatRoom chatRoom;
    
    // 创建同事对象
    User alice("Alice", &chatRoom);
    User bob("Bob", &chatRoom);
    Admin admin("Admin", &chatRoom);
    
    // 注册到中介者
    chatRoom.addColleague(&alice);
    chatRoom.addColleague(&bob);
    chatRoom.addColleague(&admin);
    
    // 发送消息
    alice.send("大家好,我是Alice");
    bob.send("Hi Alice!");
    admin.send("请遵守聊天室规则");
    
    return 0;
}

输出结果:

[管理员 Admin] [系统通知] Alice 加入了聊天室
[管理员 Admin] [系统通知] Bob 加入了聊天室
[用户 Alice] Admin: Alice 加入了聊天室
[用户 Bob] Admin: Alice 加入了聊天室
[用户 Alice] Bob: 大家好,我是Alice
[用户 Bob] Alice: Hi Alice!
[管理员 Admin] Alice: 请遵守聊天室规则
[用户 Alice] Admin: 请遵守聊天室规则
[用户 Bob] Admin: 请遵守聊天室规则

你看,Alice发消息时,她自己不会收到自己的消息。Bob和Admin会收到。这就是中介者模式里常见的「不给自己发」的逻辑。

避坑指南:我曾经在一个项目里,同事对象在析构时忘记从中介者注销自己,导致中介者持有悬空指针。后来我强制要求所有同事对象在析构函数里调用 mediator->remove(this)。如果你用智能指针,也要注意循环引用的问题——中介者持有同事的 shared_ptr,同事又持有中介者的 shared_ptr,那就死锁了。

中介者模式的好处很明显:

  • 解耦:同事对象之间不再直接依赖,修改一个同事不会影响其他同事
  • 集中控制:交互逻辑集中在中介者里,方便管理和修改
  • 减少子类:如果没有中介者,你可能需要为每对交互关系创建子类

但也要注意它的缺点:

  • 中介者可能变得臃肿:如果同事对象太多,中介者会承载所有交互逻辑,变成「上帝对象」
  • 性能瓶颈:所有通信都经过中介者,可能成为性能热点

我个人习惯是,当同事对象数量超过5个,或者交互逻辑开始变得复杂时,才考虑引入中介者模式。如果只有两三个对象互相通信,直接用观察者模式或者直接调用反而更简单。

嗯,中介者模式就聊到这儿。记住一句话:多对多变成一对多,解耦的代价是集中。用得好,代码清爽;用不好,中介者变成大泥球。


公众号:蓝海资料掘金营,微信deep3321