过长的消息链是什么
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() 每一步都返回同类对象,调用方并没有因此了解谁的内部结构,语义上也更贴近意图。