什么是迪米特法则
迪米特法则(Law of Demeter,1988 年由 Lieberherr 等提出)要求对象只与直接协作者通信,不要沿着内部结构层层穿越:a.getB().getC().doSomething() 这类写法让调用方知道了 B 和 C 的内部结构。判断标准不是点号数量,而是「是否获取了对象的内部结构再据此决策」。
基本信息
| 通俗表述 | 只与朋友交谈,不与陌生人交谈;不要对陌生人下指令 |
|---|---|
| 收益 | 调用方不依赖被穿越对象的内部结构,改动影响面收敛 |
| 不适用 | 链式流式 API、构建器与领域方法内部的链式调用不属于违规 |
| 落地手段 | 由直接协作者暴露语义方法,必要时在聚合内封装计算 |
典型使用场景
- 金额与折扣计算:把折扣规则封装在订单内部,避免外部穿越客户等级
- 权限判断:由对象自身回答「能否执行」,而不是让调用方拼装条件
- 状态校验:把多字段组合的判断放进聚合,避免散落在服务层
- 对象组装:由工厂或仓储返回完整对象,调用方不需要自己拼路径
- 重构存量:识别穿越点,逐个改为语义方法并补充单元测试
- 接口设计:对外暴露语义方法而非内部字段的结构
改写示例
改写示例
# 违规:调用方了解订单、客户与等级的内部结构
price = order.get_customer().get_level().discount()
if order.get_customer().get_level().name == 'VIP': ...
# 修复:让直接协作者完成计算与判断
price = order.discount_price() # 折扣规则封装在订单内部
if order.is_vip_customer(): ... # 判断逻辑封装在客户/订单上
# 注意:构建器与流式 API 的链式调用不属于此类问题
request = Client.builder().timeout(3).retries(2).build()违规与合规的界限
| 写法 | 判断 | 说明 |
|---|---|---|
| order.customer.level.discount() | 违规 | 穿越两层内部结构做决策 |
| order.discount_price() | 合规 | 语义方法,内部结构不外露 |
| Client.builder().build() | 合规 | 构建器式链式调用 |
| list.stream().filter(..).toList() | 合规 | 同对象链式流水线 |
判断标准是「是否获取了对象的内部结构再决策」,而不是简单的点号数量:语义方法与构建器的链式调用并不违背该原则。
为消除链式调用在中间类上堆转发方法,会带来大量样板代码,反而增加维护成本,应先考虑职责是否放错位置。