首页/开发/代码质量与评审速查/坏味道:过长的消息链(Message Chains)
重构手法62 条

坏味道:过长的消息链(Message Chains)

a.getB().getC().getD() 这类链条让调用方耦合于整条对象图:用 Hide Delegate 封装转发,或对调用方引入更贴近意图的方法;链条每多一环,改动波及面更大。

过长的消息链是什么

a.getB().getC().getD() 这类连续调用,让调用方耦合于整条对象图:它必须知道 B 有 C、C 有 D。链条上任何一环调整,调用点都要跟着改,而这样的调用点往往散在很多地方。它的反面是迪米特法则:对象只和直接的朋友说话。判断标准不是链子长短本身,而是调用方是否被迫了解本不该了解的中间结构。

基本信息

信号连续多个 getter、调用方熟悉中间对象结构
手法Hide Delegate、Extract Function、在服务方加转发方法
注意迪米特法则的现实权衡:链条是数据传输结构时不必隐藏
对照与中间人坏味道方向相反,需要找平衡点

判断与处理

  • 链条经过的对象是内部实现细节时,用转发方法把它藏起来
  • 中间对象是公开的 DTO 或配置结构时,通常不必强行封装
  • 同一链条在多个调用点反复出现,优先在服务方提供贴合意图的方法
  • 链条上存在空值风险时,返回默认值或特例对象比层层判空更好
  • 链子只出现在构造与测试拼装处,属于合理的显式依赖表达

封装方向

封装方向
// 之前:调用方必须知道 Manager 有 Department、Department 有 Leader
String name = manager.department().leader().name();
int size = manager.department().members().size();

// 之后:把意图和对象图一起收到服务方
class Manager {
    private final Department department;
    String leaderName() { return department.leaderName(); }
    int teamSize()    { return department.memberCount(); }
}

String name = manager.leaderName();
int size = manager.teamSize();

相关手法对照

手法方向适用
Hide Delegate在服务方加转发,隐藏下游调用方被迫了解内部对象图
Remove Middle Man去掉纯转发,直接暴露依赖转发方法已过半且无附加价值
Extract Function把链条的用途命名成一个函数链条本身表达一个业务意图
Introduce Special Case用特例对象消除判空链条上频繁出现空值检查
封装成大量转发方法会把问题推给另一头:中间人坏味道随之而来。折中的判断是看转发方法是否携带额外语义——只转发、无校验、无组合的纯转发行值得警惕。
流式 API 与集合管道不属于此列:stream().filter().map().toList() 每一步都返回同类对象,调用方并没有因此了解谁的内部结构,语义上也更贴近意图。

易错点

  • 封装成大量转发方法:中间人坏味道随之而来,类不断变厚
  • 链条本身是传输对象结构(DTO):强行隐藏反而绕路,取值还得层层穿透
  • 忘记处理链条上的空值:深层调用抛空指针,堆栈指向封装层而不是真实原因
  • 只在调用方提取函数:重复的链条依旧散落,改动仍然要动多处

数据来源与参考

  • 《重构》第 2 版 · 第 3 章(Message Chains)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌