13. 迪米特法则(LoD)下:实际案例——中介者模式的应用,LoD与松耦合
上一章我们聊了迪米特法则的理论,说白了就是「不要和陌生人说话」。但光有理论不够,咱们得看看实际代码里怎么落地。今天我就拿一个真实项目中的例子来讲——中介者模式,这玩意儿简直就是LoD的亲儿子。
一个让我头疼的聊天室
几年前我接手过一个即时通讯模块。需求很简单:多个用户在一个聊天室里发消息,其他人能收到。我一开始的写法,嗯,你们大概能猜到——每个用户对象直接持有其他用户的引用。
class User {
public:
void sendMessage(const std::string& msg, User* receiver) {
receiver->receive(msg);
}
void receive(const std::string& msg) {
// 处理消息
}
};
看起来挺直接对吧?但问题很快就来了。当用户数量增多,每个用户都要维护一个长长的好友列表。更可怕的是,如果有人退出聊天室,我得通知所有其他用户更新列表。这代码改起来,简直像在拆炸弹。
我曾经在这个项目里踩过一个坑:有个用户断线重连后,消息发给了已经退出的用户对象,结果程序直接崩溃。排查了一下午,发现是引用没及时清理。说白了,就是耦合太紧,牵一发而动全身。
中介者模式登场
后来我重构了这段代码,引入了中介者。你想想看,聊天室本身不就是个中介吗?用户只管把消息丢给聊天室,聊天室负责转发给其他人。用户之间不再直接通信,这就是LoD的完美体现。
class ChatMediator {
public:
void addUser(User* user) {
users_.push_back(user);
}
void broadcast(const std::string& msg, User* sender) {
for (auto* user : users_) {
if (user != sender) {
user->receive(msg);
}
}
}
private:
std::vector<User*> users_;
};
class User {
public:
User(const std::string& name, ChatMediator* mediator)
: name_(name), mediator_(mediator) {}
void send(const std::string& msg) {
mediator_->broadcast(msg, this);
}
void receive(const std::string& msg) {
// 收到消息
}
private:
std::string name_;
ChatMediator* mediator_;
};
看到区别了吗?User不再知道其他User的存在。它只认识一个中介者。中介者负责协调所有交互。这就是迪米特法则的核心——每个类的「朋友圈」越小越好。
LoD与松耦合的关系
我个人习惯把LoD看作是松耦合的实践指南。松耦合是个抽象概念,而LoD给出了具体规则:一个类只应该和它的直接朋友通信。那什么是「直接朋友」?
- 当前对象本身(this)
- 作为参数传入的对象
- 成员变量引用的对象
- 方法内部创建的对象
除此之外的,都是陌生人。别跟它们说话。
核心要点:LoD不是让你完全不通信,而是让你通过「中间人」来通信。这个中间人就是中介者。它把多对多的复杂关系,简化成了一对多的星型结构。
中介者模式的适用场景
不是所有地方都需要中介者。我一般在以下情况才会考虑:
| 场景 | 是否推荐中介者 | 原因 |
|---|---|---|
| 对象间交互复杂,网状结构 | 强烈推荐 | 中介者能显著降低耦合 |
| 交互逻辑经常变化 | 推荐 | 修改集中在中介者,不影响各对象 |
| 只有两三个对象交互 | 不推荐 | 引入中介者反而增加复杂度 |
| 性能敏感场景 | 谨慎 | 中介者可能成为性能瓶颈 |
说白了,中介者模式是用集中化换解耦。如果交互逻辑本身很简单,就别硬上中介者,否则就是过度设计了。
LoD的边界在哪里?
你可能会问:那中介者本身不也违反了LoD吗?它要跟所有用户打交道啊。
嗯,这里要注意:LoD是针对普通类而言的。中介者本身就是负责协调的,它的职责就是管理这些交互。就像项目经理要跟所有组员沟通一样,这是它的本职工作。所以中介者违反LoD是合理的——这叫「职责所在」。
我的经验:判断一个类是否违反了LoD,可以看它的方法里有没有「链式调用」。比如 a->getB()->getC()->doSomething(),这基本就是LoD的红色警报。遇到这种情况,我通常会考虑在A或B上增加一个直接方法,或者引入中介者。
用SVG理清结构
下面这张图展示了引入中介者前后的对比。左边是混乱的网状结构,右边是清晰的星型结构。
实际项目中的避坑指南
最后分享几个我在项目中总结的经验:
- 中介者别太胖:我见过有人把所有逻辑都塞进中介者,结果中介者变成了上帝类。记住,中介者只负责协调,不负责业务逻辑。
- 考虑事件驱动:如果交互特别复杂,可以用观察者模式配合中介者。中介者只管转发事件,具体处理交给各对象自己。
- 注意生命周期:中介者持有所有对象的引用,要小心内存泄漏。我习惯用弱引用或者智能指针来管理。
说白了,迪米特法则和中介者模式就是一对黄金搭档。LoD告诉你「别跟陌生人说话」,中介者告诉你「找谁传话」。两者结合,代码的维护性会提升一个档次。
下次你看到代码里到处都是 obj->getX()->getY()->doZ() 这种链式调用,不妨想想:是不是该引入一个中介者了?