组合替代继承的核心是将“变的部分”抽为独立组件,主类通过接口引用委托行为,避免子类爆炸;如支付×通知×地区组合仅需9个组件+1主类,远少于继承的24个子类。

用组合替代继承,核心是把“变的部分”抽成独立组件,让主类通过持有接口引用去委托行为,而不是靠子类分支去覆盖。这能直接砍掉因多维度叠加产生的类数量,比如支付方式×通知渠道×地区规则,继承会催生 2×3×4=24 个子类,组合只需 2+3+4=9 个组件加 1 个主类。
识别并拆解爆炸源
类爆炸通常来自多个正交变化维度耦合在继承链里。常见信号包括:
- 类名里带多个修饰词,比如 ScrollableResizableWindowWithBorder 或 WechatPaymentRefundableOrderProcessor
- 新增一个业务场景(如“支持离线重试”)就得新建一个子类,还要调整父类构造逻辑
- 不同子类只重写一两个方法,其余代码完全重复
用接口定义能力边界
每个可变行为封装为单一职责接口,不暴露内部细节:
- 支付能力 → IPaymentStrategy(只含
charge()、refund()) - 通知渠道 → INotificationSender(只含
send(message)) - 校验逻辑 → IValidationRule(只含
isValid(input))
主类(如 OrderService)只持这些接口的引用,不关心实现是谁。测试时换 Stub,上线时换 Kafka 实现,零修改主逻辑。
运行时装配,避免编译期绑定
继承把关系锁死在代码里,组合把装配权交给外部:
- 构造时传入:new OrderService(new AlipayStrategy(), new SmsSender())
- 或由 DI 容器按环境注入:开发用内存队列,生产用 RabbitMQ
- 甚至支持运行中切换:下单后调
service.setNotifier(new EmailNotifier())发确认邮件
没有 new 出一堆子类,也没有为每种组合提前写好类,扩展成本从“写类+改调用”降到“写一个组件+配一下”。
保留继承的合理位置
组合不是全盘否定继承,而是把继承收束到真正稳定的地方:
- 纯数据载体:如 UserDto、ApiResponse
,只含字段和简单 getter - 明确且不变的 is-a 关系:如 AdminUser 是 User,且权限模型长期固定
- 框架强制基类:如 Spring 的 Controller、Unity 的 MonoBehaviour,此时组合用于填充业务逻辑,而非替代框架契约
其它所有易变、可插拔、需隔离测试的部分,一律交给组合。











