首页/开发/代码质量与评审速查/手法:Replace Subclass with Delegate(以委托替换子类)
重构手法62 条

手法:Replace Subclass with Delegate(以委托替换子类)

变体组合爆炸(类型 × 场景)时继承难以扩展:改为对象持有策略/委托字段,运行时组合行为;多态从编译期继承转移到运行期装配。

子类爆炸时改用委托

当子类数量按多个维度相乘增长(会员等级、场次类型、折扣方案),继承就失效了:新增一个维度要写一整批子类,而组合出来的行为远多于真实需要。以委托替换子类把行为差异挪到运行期装配的对象上——宿主类只保留一个策略字段,变体由工厂按组合创建。

基本信息

信号子类数量按维度相乘、行为组合多于继承关系
做法策略或委托字段加工厂装配
对比行为差异是稳定封闭集合时可保留子类
代价多一层间接,排查问题需要跟进委托链

典型使用场景

  • 定价与计费:等级、周期、促销各自变化,用规则对象组合而不是子类相乘
  • 渠道与终端差异:同一业务逻辑在不同渠道的展示规则,用委托替换渠道子类
  • 存储后端:读写策略作为可替换对象注入,避免为每种组合建子类
  • 序列化格式:格式差异由委托实现,运行时按配置选择
  • 迁移旧继承体系:先保留子类接口,内部改为委托,再逐步删除子类

形态示意

形态示意
# 之前:用继承表达每个变体
class Booking
class PremiumBooking extends Booking   # 等级维度
class WeekendBooking extends Booking   # 场次维度
class GroupBooking extends Booking     # 人数维度
# 组合出现后 → PremiumWeekendGroupBooking ... 子类数量按维度相乘

# 之后:一个委托字段承接变化
booking.pricing = PricingPlan([groupDiscount, weekendRule])

子类还是委托的判断维度

维度倾向子类倾向委托
变体数量少且封闭多且组合增长
变化时点编译期确定运行期装配
差异范围只覆写少量方法整体行为可替换
排查成本调用链直白需跟进委托对象
委托链会把调用栈拉长:日志与调试器里看到的是多层转发,定位问题要先看装配点(工厂或配置)而不是宿主类。委托对象用业务语义命名,比用 Ref 与 Impl 之类后缀更易读。
《重构》第 2 版第 12 章把这一手法放在继承处理一章中,与撤销子类配套使用:先判断差异是数据还是行为,数据用字段或枚举,行为用委托。

易错点

  • 委托对象生命周期失控:共享的策略对象被就地修改,所有引用它的单据一起出错,策略应设计为不可变或按对象克隆
  • 装配逻辑散落各处:创建规则组合的代码在多个业务分支里复制,应集中到工厂
  • 只删子类不留兼容:外部代码仍按子类类型判断,动手前先全局检索类型引用
  • 过度委托:变体只有一个且不会再增时引进委托,多一层间接没有收益

数据来源与参考

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