迪米特法则(LoD):只和"直接的朋友"说话
更新时间:2026-08-25。本文从语义与意义两个层面论述迪米特法则(Law of Demeter, LoD,也称最少知识原则 Least Knowledge Principle):它约束对象之间的沟通范围——不要通过中间人层层调用别人的内部。
一、语义:LoD 到底在说什么
LoD 的经典表述:一个对象应该对它需要协作的其他对象保持最少了解(Each unit should have only limited knowledge about other units)。
更操作的表述("只和直接朋友说话"):一个对象的方法只允许调用以下对象的成员:
| 允许调用的对象 | 例子 |
|---|---|
| 自己(this 的方法/字段) | this.total() |
| 自己创建/持有的对象 | 成员字段、局部 new 的对象 |
| 作为方法参数传入的对象 | 只调用参数对象的方法 |
| 返回的集合/对象(临时) | 返回值上的方法可以调,但不能再深挖 |
| 全局对象(如单例) | 谨慎 |
语义要点:LoD 禁止的是"层层深入"——a.getB().getC().getD().doSomething() 这种"消息链"(train wreck / chained calls)。调用方为了完成一件事,必须了解 B、C、D 的整个结构,任何一层内部变化都波及调用方。
与 ISP 的区别:ISP 管"接口的宽度"(方法数量),LoD 管"沟通的深度"(跨越多少层对象)。ISP 是静态切分,LoD 是运行时行为约束。
常见误解:LoD 不等于"禁止链式调用"(builder.setA().setB() 是 Fluent 风格,链上每一步都是同一对象或返回值契约的一部分,不属于"探知他人内部");LoD 禁止的是为获取数据而穿越对象边界。
二、意义:为什么 LoD 决定系统的可维护性
- 降低耦合面:对象知道得越少,越不依赖他人内部结构;他人内部变了,知道的少的一方不需要跟着改。
- 封装不被撕破:getter 泛滥会撕破封装——调用方通过 getter 链拼凑业务逻辑,等于把被调用方的内部决策搬到了外部。
- 变更传播半径缩小:
a.b.c.d.x中任何一环改名/改结构,所有链路调用点全爆——信息传播半径等于耦合半径。 - 性能视角(本仓库主线):getter 链每层都可能引入缓存未命中、虚函数跳转;跨对象取数据往往还伴随锁的获取。用 LoD 约束"数据应被拥有方处理"后,处理就近发生,减少跨层访问(这与"数据局部性"优化的直觉一致)。
三、违反与守好:对比类图
违反 LoD 的样子——层层深入取数据:

守住 LoD 的样子——数据由拥有方负责交付:

对比分析
| 维度 | 违反 LoD(消息链) | 守住 LoD(直接朋友) |
|---|---|---|
| 调用方知识 | 知道 3~4 层内部结构 | 只知直接朋友一个接口 |
| 变更传播 | 任一层改名全链路爆 | 内部重组只影响拥有方 |
| 封装 | getter 链撕破封装 | 能力由拥有方主动提供 |
| 测试 | 需要构造整条对象链 | 只需 mock 直接朋友 |
| 复用 | 取数逻辑散落在各调用方 | 取数逻辑集中在拥有方 |
四、如何判断:实践检查清单
| 症状 | 判断 |
|---|---|
出现 a.getB().getC().getX() 两级以上 getter 链 | 已违反,把取数收进拥有方 |
| 一个类的方法参数里出现大量无关对象(只为转手取数据) | 已违反,应改为直接传入所需值 |
| 改一个类的字段名,十几个调用方跟着改 | 已违反,调用方知道太多了 |
| 工具类里写满"从 A 拿 B 再从 B 拿 C"的搬运代码 | 已违反 |
| 对象只与自己持有的、传入的、返回的对象的直接方法交互 | ✅ 符合 LoD |
边界警示:LoD 与"性能追求少跳转"在这里可能冲突——有些链路在热点路径上"绕道拥有方"会多一层调用。先保证正确与可维护,再用剖析工具验证热点;若确为热点,可用缓存/扁平化数据(DTO)在拥有方内部聚合,而不是把链路暴露给调用方。
五、与设计模式的关系
LoD 是"中介/门面/外观"类模式的直接动机:
| 模式 | LoD 的体现 |
|---|---|
| Facade | 门面是"直接朋友",子系统细节对客户端隐藏 |
| Mediator | 同事对象不互相认识,只与中介者通信 |
| Proxy | 代理屏蔽真实对象的访问路径 |
| Observer | 观察者只依赖主题接口,不深入主题内部 |
| Command | 请求发起方只依赖命令接口,不接触执行者链 |
反模式警示:消息链(train wreck)是 LoD 违约的典型信号;重构手段是 Move Method / 方法归位——把"跨越链路才能拿到的东西"变成拥有方主动提供的方法。
一句话总结
迪米特法则要求对象只与它的直接朋友通信,禁止为取数据而穿越多层对象边界;调用方知识面收窄到最小,封装不被 getter 链撕破,变更传播半径随之缩小——它与 ISP 一个管接口宽度、一个管沟通深度,共同收紧了对象间的耦合面。