从LoD到中介者模式:中介者如何解耦多对多关系

聊到中介者模式,我得先提一个设计原则——迪米特法则(LoD)。说白了就是:不要和陌生人说话。每个对象只跟它直接相关的对象通信,别绕来绕去。

但现实项目里,对象之间常常是“多对多”的关系。比如一个聊天室,多个用户互相发消息。如果每个用户都直接持有其他用户的引用……嗯,那代码就乱成一锅粥了。

多对多关系的痛点

我早年接手过一个旧系统,里面有个“消息分发”模块。每个模块都直接调用其他模块的接口。结果呢?

  • 新增一个模块,要改 N 个地方
  • 删除一个模块,要排查所有引用
  • 改一个接口,连锁反应

这就是典型的多对多耦合。每个对象都像蜘蛛网上的一个节点,牵一发动全身。

核心问题:对象之间互相知道太多,导致系统难以维护、扩展、测试。

中介者模式登场

中介者模式就是来解决这个问题的。它引入一个中介者对象,让所有对象只跟中介者通信,不直接互相调用。

你想想看,就像现实中的房产中介。买房的人不用挨个找房东,房东也不用自己带人看房。中介统一协调,双方只跟中介打交道。

在代码里,这个模式长这样:

// 中介者接口
class Mediator {
public:
    virtual void send(const std::string& msg, Colleague* sender) = 0;
    virtual ~Mediator() = default;
};

// 同事类(Colleague)
class Colleague {
protected:
    Mediator* mediator_;
public:
    explicit Colleague(Mediator* m) : mediator_(m) {}
    virtual void send(const std::string& msg) {
        mediator_->send(msg, this);
    }
    virtual void receive(const std::string& msg) = 0;
};

// 具体同事
class User : public Colleague {
private:
    std::string name_;
public:
    User(const std::string& name, Mediator* m) 
        : Colleague(m), name_(name) {}
    
    void receive(const std::string& msg) override {
        std::cout << name_ << " received: " << msg << std::endl;
    }
};

// 具体中介者
class ChatRoom : public Mediator {
private:
    std::vector<Colleague*> users_;
public:
    void addUser(Colleague* user) {
        users_.push_back(user);
    }
    
    void send(const std::string& msg, Colleague* sender) override {
        for (auto* user : users_) {
            if (user != sender) {
                user->receive(msg);
            }
        }
    }
};

你看,User 之间不直接通信,全通过 ChatRoom 转发。新增一个用户,只需要注册到聊天室就行,其他用户不用改。

中介者 vs 观察者:到底有啥区别?

这个问题我经常被问到。很多人觉得两者有点像,都是解耦。但本质不同。

维度 中介者模式 观察者模式
通信方向 多对多,双向 一对多,单向(主题→观察者)
控制中心 中介者集中控制逻辑 主题只负责通知,不控制
典型场景 聊天室、UI控件协调 事件监听、状态变化通知
耦合度 同事之间零耦合 观察者依赖主题接口

举个例子你就明白了:

  • 观察者模式:一个按钮被点击,通知所有监听器。监听器之间互不知道。
  • 中介者模式:一个文本框输入内容,中介者决定要不要禁用提交按钮、要不要清空另一个输入框。多个控件之间需要协调。

说白了,观察者适合“广播通知”,中介者适合“复杂交互协调”。

我的经验:如果只是“A变了,通知B、C、D”,用观察者就够了。如果B变了,C要更新,D要禁用,E要弹窗……这种多对多协调,果断上中介者。

中介者模式的优缺点

任何模式都有代价。中介者模式也不例外。

优点:

  • 减少对象之间的耦合,每个对象只依赖中介者
  • 集中控制交互逻辑,方便修改和维护
  • 对象可以独立变化,互不影响

缺点:

  • 中介者可能变得臃肿,承担太多职责
  • 如果中介者出问题,整个系统受影响
  • 增加了系统的复杂度(多了一个角色)
避坑指南:我曾经在一个项目中,把所有交互逻辑都塞进中介者,结果中介者变成了“上帝对象”,几千行代码,改都不敢改。后来拆成了多个小中介者,每个负责一组相关交互,才救回来。

什么时候用中介者模式?

我个人习惯,遇到以下情况会考虑:

  1. 对象之间有多对多的交互,且交互逻辑复杂
  2. 交互逻辑经常变化,需要集中管理
  3. 对象之间不应该互相知道对方的存在

但别滥用。如果只是两三个对象简单通信,直接调用反而更清晰。

一张图看懂中介者模式

下面这张图展示了中介者模式的核心结构:

中介者模式结构图 中介者 同事A 同事B 同事C 只与中介者通信 只与中介者通信 只与中介者通信 核心思想 所有同事对象只与中介者交互 中介者封装了对象之间的交互逻辑

从图里能清楚看到:所有同事都只连到中介者,彼此之间没有直接连线。这就是解耦的关键。

总结

中介者模式,说白了就是把“多对多”变成“一对多”。中介者一个人扛起所有协调工作,其他对象只管做好自己的事。

我个人的建议是:先画交互图。如果发现对象之间的连线密密麻麻,那就是中介者该出场的时候了。但别忘了,中介者本身也要控制好复杂度,别让它变成新的“上帝对象”。

嗯,今天就聊到这儿。下次遇到多对多耦合,试试中介者模式,你会回来感谢我的。


专注资料整理