1. 一个具体问题
一段发货代码沿着“订单—客户—账户—地址簿—默认地址”连续取值。地址簿结构只改了一层,多个与地址管理无关的模块就一起报错。代码知道得太多,使局部调整无法保持局部。
2. 一句话解释
迪米特法则主张模块只与必要的直接协作者交互,减少对远处对象内部结构的了解。
3. 出处与原意
该法则出自东北大学 Demeter 研究项目,常用“只与直接朋友交流”帮助记忆。原始讨论与面向对象设计及结构变化有关。口语化的“不要写多个点”只是粗略提示,并不是准确的代码判定规则。
4. 原理与机制
长链访问容易让调用方依赖多个中间对象的布局、空值规则和生命周期。改为向掌握知识的对象请求所需行为,可以把结构变化限制在内部。例如让订单提供配送信息,而不是让每个调用者自己穿透客户档案。问题不是点号本身:流式构造器返回同一抽象层的对象,通常不具有相同耦合;反之,没有点号也可能通过全局变量依赖大量内部细节。封装目标是减少知识扩散,而非增加机械转发方法。
减少结构依赖不等于禁止数据传递。一个服务可以向另一个服务传递所需的不可变数据,而无需暴露整棵对象图;这往往比让对方随时回调内部对象更清楚。还要看边界是否保留必要语义:只传一个裸数字,可能丢失币种或单位。优秀的最少知识设计既限制不必要细节,也让使用者获得完成任务所需的完整、明确的信息。
5. 一个完整案例
假设案例:配送模块从订单一路取得客户默认地址,结果客户修改地址后,已下单包裹也被送往新地址。团队重新定义订单的配送职责:下单时保存地址快照,配送模块只读取订单的配送目的地。客户地址簿如何重构不再影响历史订单;订单还统一验证缺失门牌号等业务规则。这个重构同时修复了语义问题,说明好的封装不是把原链条包进一个同名函数就结束,而是把数据的时间含义和责任归属说清楚。
6. 适用条件与反例
适合对象层级复杂、领域结构经常调整的应用。对于专门传输数据的简单记录结构,适度访问字段并不自动构成坏设计。如果为遵守表面规则产生几十个无意义转发方法,可能只是把复杂性藏起来,应重新检查边界是否合理。
7. 今天可以尝试的行动
搜索一处跨越多层对象的访问,列出调用方为什么需要知道每一层。把真正需要的业务结果命名,例如“取得订单配送目的地”,再决定由谁负责计算或保存。改完后尝试调整内部结构,检查外部是否仍需一起修改。