用组合替换继承是降低变量脆弱性、避免层级爆炸最有效的路径:将行为抽成接口由主类持有,隔离高风险变量(如重写的虚方法、protected字段、基类默认行为、构造函数隐式依赖),保留继承于稳定is-a关系、纯数据类或框架强制基类,并通过DI实现运行时动态切换。

直接用组合替换易变的父类字段和虚方法,把行为抽成接口由主类持有,而不是让子类去继承和重写——这是降低变量脆弱性、避免层级爆炸最有效的实战路径。
识别并隔离高风险变量
这些变量一旦改动,会像多米诺骨牌一样波及整个继承链:
- 被子类重写的虚方法(尤其是 Process、Validate 这类控制流程的方法)
- protected 字段或属性(子类直接读写,等于把内部状态暴露给所有后代)
- 基类中悄悄新增的默认行为(比如自动记录日志、统一拦截参数)
- 构造函数里隐式依赖的初始化顺序(子类忘了调 super() 就可能半初始化)
用接口+组合重构变量承载逻辑
不靠父类“塞”行为,而是让主类“拿”行为。例如订单处理系统:
- 把校验、计税、通知等职责分别定义为 IOrderValidator、IOrderTaxCalculator、IOrderNotifier
- OrderProcessor 类不再继承任何业务基类,只持有一组策略对象
- 测试时注入 StubValidator,海外订单注入 VATCalculator,完全不用改主类或建新子类
保留继承的合理边界
继承不是要消灭,而是要退守到真正稳定、语义清晰的位置:
- 领域内明确且不会随业务变化的 is-a 关系(如 AdminUser 是 User)
- 纯数据容器类(仅含 Id、CreatedAt 等字段和简单 getter/setter)
- 框架强制要求的基类(如 ControllerBase),此时组合用于填充业务,而非替代它
运行时注入代替编译期绑定
组合天然支持动态切换,这是继承做不到的:
- 同一个 OrderProcessor 实例,下单前可设 setTaxCalculator(new ZeroTax()),支付后换 setNotifier(new SmsNotifier())
- 通过 DI 容器按环境注入:开发用内存队列,生产用 Kafka
- 故障时临时替换成 FailFastNotifier,主流程结构零改动

















