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理清结构

下面这张图展示了引入中介者前后的对比。左边是混乱的网状结构,右边是清晰的星型结构。

没有中介者(网状) A B C D 有中介者(星型) 中介者 A B C D → 重构 → 每个对象只与中介者通信,不再直接依赖其他对象 耦合度从 O(n²) 降低到 O(n)

实际项目中的避坑指南

最后分享几个我在项目中总结的经验:

  • 中介者别太胖:我见过有人把所有逻辑都塞进中介者,结果中介者变成了上帝类。记住,中介者只负责协调,不负责业务逻辑。
  • 考虑事件驱动:如果交互特别复杂,可以用观察者模式配合中介者。中介者只管转发事件,具体处理交给各对象自己。
  • 注意生命周期:中介者持有所有对象的引用,要小心内存泄漏。我习惯用弱引用或者智能指针来管理。

说白了,迪米特法则和中介者模式就是一对黄金搭档。LoD告诉你「别跟陌生人说话」,中介者告诉你「找谁传话」。两者结合,代码的维护性会提升一个档次。

下次你看到代码里到处都是 obj->getX()->getY()->doZ() 这种链式调用,不妨想想:是不是该引入一个中介者了?


专注资料整理