中介者模式:让对象之间的“社交”变得优雅
说实话,我在刚接触面向对象设计的那几年,最头疼的问题之一就是对象之间的通信。你想想看,一个系统里十几个类互相调用,A知道B,B知道C,C又反过来调用A……这种网状结构维护起来简直是一场噩梦。我记得有一次接手一个遗留项目,光是为了搞清楚一个按钮点击后到底影响了多少个对象,我就花了整整两天时间。
后来我学到了中介者模式,才恍然大悟——原来对象之间的“社交”可以这么优雅。
模式动机与定义
先说说动机。为什么需要中介者?
假设你正在开发一个聊天室应用。每个用户都可以给其他用户发消息。如果让用户对象直接互相通信,那每个用户都得知道其他所有用户的存在。新增一个用户,所有现有用户都得更新。删除一个用户,又得通知所有人。这种耦合度,说白了就是给自己挖坑。
中介者模式的核心思想很简单:用一个中介对象来封装一组对象之间的交互。这些对象不再直接互相引用,而是通过中介者来通信。
定义:中介者模式(Mediator Pattern)定义了一个中介对象来封装一组对象之间的交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,并且可以独立地改变它们之间的交互。
我个人习惯把中介者比作“交通指挥中心”。飞机之间不需要互相喊话,它们只需要跟塔台通信。塔台知道所有飞机的位置和意图,负责协调。这样一来,每架飞机只需要关心塔台,而不是其他几十架飞机。
角色与结构
中介者模式涉及以下几个角色:
| 角色 | 名称 | 职责 |
|---|---|---|
| Mediator | 抽象中介者 | 定义通信接口,声明同事对象之间的交互方法 |
| ConcreteMediator | 具体中介者 | 实现中介者接口,协调各个同事对象的交互逻辑 |
| Colleague | 抽象同事类 | 定义同事对象的公共接口,持有中介者的引用 |
| ConcreteColleague | 具体同事类 | 实现自己的业务逻辑,通过中介者与其他同事通信 |
结构图如下:
你看这个结构,同事类之间没有直接连线,所有通信都经过中介者。这就是解耦的关键。
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系统、聊天系统、工作流引擎等场景中非常实用。下次你遇到对象之间通信混乱的问题,不妨想想——是不是该请个“管家”了?