首页/开发/代码质量与评审速查/坏味道:依恋情结(Feature Envy)
重构手法62 条

坏味道:依恋情结(Feature Envy)

函数频繁访问另一个对象的内部数据:说明职责放错了地方,用 Move Function 搬过去;数据与行为同处一个模块,边界才清晰。

什么是依恋情结

依恋情结指某个函数频繁访问另一个对象的内部数据,访问别的对象比访问自己所在对象还多。这通常说明职责放错了位置:数据和行为分了家。处方是用移动函数把方法搬到它真正依赖的数据所在的类里,让数据与行为待在一起,调用方依赖也随之变少。

基本信息

典型信号函数里别的对象前缀出现次数超过自身
处方移动函数,移动后删除原方法
例外情况策略、访问者等模式中有意为之的分离
相邻问题数据泥团、基本类型偏执常同时出现
收益职责归位,调用方依赖变少

典型使用场景

  • 工具类里塞满对某个实体字段的计算逻辑
  • 服务方法中大部分代码在读写另一个对象的属性
  • 转换方法只读取另一个对象的数据,自己几乎没有状态
  • 表单校验写在控制器里,但字段与规则都属于领域对象
  • 报表汇总逻辑放在与数据完全无关的模块

移动前后

移动前后
// 之前:total 依赖 Purchase 的内部数据,却不在 Purchase 里
double total(Purchase p) {
    return p.items().stream().mapToDouble(Item::price).sum();
}

// 之后:搬到数据所在的地方
class Purchase {
    double total() { return items.stream().mapToDouble(Item::price).sum(); }
}
// 调用方从 total(p) 变为 p.total(),原方法删除

信号与判断

信号含义处理
访问别的对象多于自身职责放错了位置把方法移动到数据所在类
只用到对方一两个字段耦合面很窄考虑直接传参而不是传整个对象
多个函数都依恋同一对象该对象的职责被外置集中搬回该对象
例外情况要说清楚:策略、访问者、双重分发等模式里,行为与数据的分离是刻意的。判断的关键在于,这是有意设计还是长期演化出的错位。
移动后保留一个转发方法会让调用链变长、排查困难;如果确实需要兼容期,写明原因与后续删除计划。

易错点

  • 机械按访问计数搬方法:忽略领域语义,做出贫血模型,行为仍然无处安放
  • 移动后保留委托转发又不标注:调用链变长,排查问题时绕圈
  • 把小函数搬进本已巨大的类:大对象职责进一步膨胀,问题只是转移
  • 只搬方法不搬相关字段:数据与行为再次分离,过一阵又回到原点

数据来源与参考

  • 《重构》第 2 版 · 第 3 章(Feature Envy)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌