子类爆炸时改用委托
当子类数量按多个维度相乘增长(会员等级、场次类型、折扣方案),继承就失效了:新增一个维度要写一整批子类,而组合出来的行为远多于真实需要。以委托替换子类把行为差异挪到运行期装配的对象上——宿主类只保留一个策略字段,变体由工厂按组合创建。
基本信息
| 信号 | 子类数量按维度相乘、行为组合多于继承关系 |
|---|---|
| 做法 | 策略或委托字段加工厂装配 |
| 对比 | 行为差异是稳定封闭集合时可保留子类 |
| 代价 | 多一层间接,排查问题需要跟进委托链 |
典型使用场景
- 定价与计费:等级、周期、促销各自变化,用规则对象组合而不是子类相乘
- 渠道与终端差异:同一业务逻辑在不同渠道的展示规则,用委托替换渠道子类
- 存储后端:读写策略作为可替换对象注入,避免为每种组合建子类
- 序列化格式:格式差异由委托实现,运行时按配置选择
- 迁移旧继承体系:先保留子类接口,内部改为委托,再逐步删除子类
形态示意
形态示意
# 之前:用继承表达每个变体
class Booking
class PremiumBooking extends Booking # 等级维度
class WeekendBooking extends Booking # 场次维度
class GroupBooking extends Booking # 人数维度
# 组合出现后 → PremiumWeekendGroupBooking ... 子类数量按维度相乘
# 之后:一个委托字段承接变化
booking.pricing = PricingPlan([groupDiscount, weekendRule])子类还是委托的判断维度
| 维度 | 倾向子类 | 倾向委托 |
|---|---|---|
| 变体数量 | 少且封闭 | 多且组合增长 |
| 变化时点 | 编译期确定 | 运行期装配 |
| 差异范围 | 只覆写少量方法 | 整体行为可替换 |
| 排查成本 | 调用链直白 | 需跟进委托对象 |
委托链会把调用栈拉长:日志与调试器里看到的是多层转发,定位问题要先看装配点(工厂或配置)而不是宿主类。委托对象用业务语义命名,比用 Ref 与 Impl 之类后缀更易读。
《重构》第 2 版第 12 章把这一手法放在继承处理一章中,与撤销子类配套使用:先判断差异是数据还是行为,数据用字段或枚举,行为用委托。