中介者模式:让对象之间的“社交”变得优雅

说实话,我在刚接触面向对象设计的那几年,最头疼的问题之一就是对象之间的通信。你想想看,一个系统里十几个类互相调用,A知道B,B知道C,C又反过来调用A……这种网状结构维护起来简直是一场噩梦。我记得有一次接手一个遗留项目,光是为了搞清楚一个按钮点击后到底影响了多少个对象,我就花了整整两天时间。

后来我学到了中介者模式,才恍然大悟——原来对象之间的“社交”可以这么优雅。

模式动机与定义

先说说动机。为什么需要中介者?

假设你正在开发一个聊天室应用。每个用户都可以给其他用户发消息。如果让用户对象直接互相通信,那每个用户都得知道其他所有用户的存在。新增一个用户,所有现有用户都得更新。删除一个用户,又得通知所有人。这种耦合度,说白了就是给自己挖坑。

中介者模式的核心思想很简单:用一个中介对象来封装一组对象之间的交互。这些对象不再直接互相引用,而是通过中介者来通信。

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

我个人习惯把中介者比作“交通指挥中心”。飞机之间不需要互相喊话,它们只需要跟塔台通信。塔台知道所有飞机的位置和意图,负责协调。这样一来,每架飞机只需要关心塔台,而不是其他几十架飞机。

角色与结构

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

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

结构图如下:

Mediator 抽象中介者 ConcreteMediator 具体中介者 实现 Colleague 抽象同事类 ConcreteColleagueA ConcreteColleagueB ConcreteColleagueC 中介者协调通信

你看这个结构,同事类之间没有直接连线,所有通信都经过中介者。这就是解耦的关键。

C++代码实现

下面我用一个聊天室的例子来演示。这个例子虽然简单,但足够说明问题。

// 前置声明
class User;

// 抽象中介者
class ChatMediator {
public:
    virtual ~ChatMediator() = default;
    virtual void sendMessage(const std::string& message, User* sender) = 0;
    virtual void addUser(User* user) = 0;
};

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

// 具体中介者
class ChatRoom : public ChatMediator {
private:
    std::vector<User*> users;
public:
    void addUser(User* user) override {
        users.push_back(user);
    }
    
    void sendMessage(const std::string& message, User* sender) override {
        for (User* user : users) {
            // 不给自己发消息
            if (user != sender) {
                user->receive(sender->getName() + ": " + message);
            }
        }
    }
};

// 具体同事类
class ConcreteUser : public User {
public:
    ConcreteUser(const std::string& name, ChatMediator* med) 
        : User(name, med) {}
    
    void send(const std::string& message) override {
        std::cout << name << " 发送消息: " << message << std::endl;
        User::send(message);
    }
};

// 使用示例
int main() {
    ChatRoom chatRoom;
    
    ConcreteUser alice("Alice", &chatRoom);
    ConcreteUser bob("Bob", &chatRoom);
    ConcreteUser charlie("Charlie", &chatRoom);
    
    chatRoom.addUser(&alice);
    chatRoom.addUser(&bob);
    chatRoom.addUser(&charlie);
    
    alice.send("大家好!");
    bob.send("嗨,Alice!");
    charlie.send("下午好!");
    
    return 0;
}

个人经验:我在一个即时通讯项目中用过这个模式。当时有群聊、私聊、系统通知三种消息类型。如果不用中介者,每个用户对象都要维护三种不同的发送逻辑,代码会变得非常臃肿。用了中介者之后,所有消息路由逻辑都集中在一个地方,维护起来轻松多了。

GUI交互示例

中介者模式在GUI系统中特别常见。你想想看,一个对话框里有按钮、文本框、下拉列表、复选框……这些控件之间经常需要联动。比如:

  • 点击“全选”复选框,所有子项复选框都被选中
  • 文本框内容变化时,“提交”按钮的可用状态需要更新
  • 下拉列表选择不同选项,其他控件的显示内容要跟着变

如果让这些控件直接互相引用,那代码会变成一团乱麻。用中介者模式就清爽多了。

// GUI控件的中介者示例
class DialogMediator;

// 抽象控件
class Widget {
protected:
    DialogMediator* mediator;
public:
    Widget(DialogMediator* med) : mediator(med) {}
    virtual ~Widget() = default;
    virtual void changed() = 0;
};

// 中介者
class DialogMediator {
private:
    class CheckBox* selectAll;
    class CheckBox* item1;
    class CheckBox* item2;
    class Button* submitBtn;
public:
    void setSelectAll(class CheckBox* cb) { selectAll = cb; }
    void setItem1(class CheckBox* cb) { item1 = cb; }
    void setItem2(class CheckBox* cb) { item2 = cb; }
    void setSubmitBtn(class Button* btn) { submitBtn = btn; }
    
    void widgetChanged(Widget* widget);
};

// 复选框
class CheckBox : public Widget {
private:
    bool checked;
    std::string label;
public:
    CheckBox(DialogMediator* med, const std::string& lbl) 
        : Widget(med), checked(false), label(lbl) {}
    
    void setChecked(bool state) { checked = state; }
    bool isChecked() const { return checked; }
    
    void click() {
        checked = !checked;
        changed();  // 通知中介者
    }
    
    void changed() override {
        mediator->widgetChanged(this);
    }
};

// 按钮
class Button : public Widget {
private:
    bool enabled;
    std::string text;
public:
    Button(DialogMediator* med, const std::string& txt) 
        : Widget(med), enabled(false), text(txt) {}
    
    void setEnabled(bool state) { enabled = state; }
    bool isEnabled() const { return enabled; }
    
    void changed() override {
        // 按钮本身不需要通知中介者
    }
};

// 中介者的协调逻辑
void DialogMediator::widgetChanged(Widget* widget) {
    if (widget == selectAll) {
        bool state = selectAll->isChecked();
        item1->setChecked(state);
        item2->setChecked(state);
    }
    
    // 只要有一个子项选中,提交按钮就可用
    bool anyChecked = item1->isChecked() || item2->isChecked();
    submitBtn->setEnabled(anyChecked);
}

避坑指南:我曾经在一个项目中把中介者写得过于“万能”,结果中介者类膨胀到几千行,变成了一个“上帝对象”。记住,中介者模式是为了解耦,不是为了把所有逻辑都塞到一个类里。如果中介者变得太复杂,可以考虑拆分成多个小中介者,或者用观察者模式来辅助。

嗯,这里要注意一点:中介者模式虽然好,但也不是万能的。如果你的对象之间交互很简单,直接调用反而更清晰。别为了用模式而用模式,那叫过度设计。

总结一下中介者模式的优缺点:

优点 缺点
减少类之间的耦合,每个类只依赖中介者 中介者可能变得复杂,成为“上帝对象”
将多对多的交互简化为一对多 中介者的设计需要仔细权衡,否则容易成为瓶颈
新增同事类时,只需修改中介者,符合开闭原则 如果同事类之间交互逻辑频繁变化,中介者也要频繁修改

说白了,中介者模式就是给对象们请了一个“管家”。管家负责协调一切,对象们只管做好自己的事。这个模式在GUI系统、聊天系统、工作流引擎等场景中非常实用。下次你遇到对象之间通信混乱的问题,不妨想想——是不是该请个“管家”了?

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