中介者模式:模式动机与定义
先聊个我早年踩过的坑。
那时候我在做一个即时通讯的后台模块。每个用户对象都要跟其他用户对象打交道——发消息、拉黑、加好友、状态同步。你想想看,如果每个对象都直接持有其他对象的引用,代码会变成什么样?
嗯,我当时的代码里,一个User类里塞了十几个指针,指向其他User。每次新增一个交互行为,就得改好几个类的接口。更别提调试了——对象之间的调用链绕来绕去,跟一团乱麻似的。
后来我重构了那套代码,引入了中介者模式。说白了,就是让所有对象不再直接通信,而是通过一个中介者来转发消息。这个中介者负责协调各个对象之间的交互逻辑。
中介者模式的定义:用一个中介对象来封装一系列对象之间的交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,并且可以独立地改变它们之间的交互。
这个模式的核心思想其实很简单:多对多的通信,转变成一对多的通信。所有同事对象只跟中介者打交道,中介者知道所有同事对象,但同事对象之间互不知晓。
角色与结构
中介者模式涉及以下几个角色:
| 角色 | 名称 | 职责 |
|---|---|---|
| Mediator | 抽象中介者 | 定义通信接口,声明同事对象之间的交互方法 |
| ConcreteMediator | 具体中介者 | 实现中介者接口,协调各个同事对象的交互逻辑 |
| Colleague | 抽象同事类 | 持有中介者的引用,定义自身的业务方法 |
| ConcreteColleague | 具体同事类 | 实现自身的业务逻辑,通过中介者与其他同事通信 |
我画了一张结构图,你看一眼就明白了:
你看这个图,同事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