首页/开发/代码质量与评审速查/手法:Remove Subclass / Replace Subclass with Delegate(撤销子类)
重构手法62 条

手法:Remove Subclass / Replace Subclass with Delegate(撤销子类)

子类的差异只剩数据或极少量分支时,撤销子类后用字段、枚举或委托表达:类数量减少、结构更直白;先确认没有以类型身份参与多态分派的逻辑。

差异只剩数据就撤销子类

当子类的差异只剩一点数据或极少量分支时,例如只是换了一个字段值或一个方法返回值,继承带来的复杂度就不值得了:改用字段、枚举或委托表达同样的差异,类数量减少、结构更直白,序列化与测试也跟着变简单。

基本信息

信号子类只改构造或单个方法,分支旋钮其实是数据
做法字段或枚举替代,也可以改为委托对象
前提没有外部代码按子类类型判断
配套动手前后都要全局检索类型引用

典型使用场景

  • 枚举化:若干子类只是同一字段取不同值,例如角色、级别、状态
  • 字段化:子类的唯一区别就是构造参数,用字段承接即可
  • 委托化:差异未来还会继续增长时,改成持有策略对象的委托字段
  • 继承体系收尾:多轮重构后只剩一个子类,直接合并回父类
  • 序列化简化:去掉类名参与的类型标识,存储与接口更稳定

形态示意

形态示意
# 之前:子类只是换了一个方法或一段构造参数
class Engineer extends Person        # 唯一区别是角色取值
class Manager  extends Person

# 之后:用字段表达差异,类数量收敛
class Person
    field name
    field role                       # role 是数据而不是类型

撤销前的检查项

检查项为什么要查怎么查
类型判断外部代码可能按类型走不同分支全局检索子类名与类型判断语句
序列化存储或协议里可能写入类名检索序列化配置与历史数据字段
工厂与注册表创建点可能按类型映射检索实例化该子类的位置
测试替身测试可能依赖具体子类做桩检索测试代码里的子类引用
推荐顺序:先把所有按类型分派的逻辑收敛到工厂或一张映射表,再删除子类。否则删到一半会出现大量条件判断散落在调用方,重构被迫回退。
差异只是暂时相同时要谨慎合并:若两个子类未来会真正分化,合并后还得重新拆类;判断依据是业务上是否真有独立演化的可能,而不是当前代码看起来像。

易错点

  • 外部代码仍按子类类型判断:撤销前先全局检索,否则运行时会走进错误分支
  • 序列化依赖类名:改类结构需评估存储与协议兼容,必要时保留类型字段做映射
  • 只用当前相似度做判断:差异其实还会分化,合并后很快要重新拆类
  • 撤销后类名与语义不符:父类里塞满角色专属字段,出现只有某些取值才有意义的伪通用类

数据来源与参考

  • 《重构》第 2 版 · 第 12 章(Remove Subclass)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌