组合优于继承的核心是将“我是一个什么”转变为“我拥有什么”,通过拆解日志、校验等通用能力为独立组件,构造注入接口依赖,运行时动态组装,严守生命周期与边界。

直接用组合替代臃肿继承体系,核心不是删代码,而是把“我是一个什么”换成“我拥有什么”。当类名里开始出现多个修饰词、新增一个业务就要加一个子类、或者改父类一动就崩——说明继承已从支撑变成枷锁,该用组合来解耦了。
先拆掉那些不该属于身份的功能
继承树臃肿,往往因为把日志、校验、重试、序列化这些通用能力硬塞进父类。它们不是“订单”或“用户”的本质,而是它“拥有的能力”。把这些拎出来,各自封装成小而专的组件:
- Logger 接口 + ConsoleLogger / FileLogger 实现
- Validator 接口 + EmailValidator / LengthValidator 实现
- RetryPolicy 类只管重试逻辑,不耦合网络或业务
每个组件只做一件事,接口最小化。避免出现“全能工具类”,那是继承退化后的典型症状。
用构造注入代替 extends
原来写 class OrderService extends BaseService,现在改为 class OrderService,内部只持依赖字段:
- 通过构造函数传入 logger、validator、paymentStrategy 等实例
- 依赖类型用接口(如
Logger),而非具体实现(如FileLogger) - 可选组件用可空类型或 Optional 包装,不强制初始化
这样测试时直接传 Mock 对象,上线时换真实实现,主逻辑一行都不用动。
运行时组装,别在编译期锁死
继承把关系定死在代码里;组合把装配权交给外部,支持动态切换:
- 下单时根据用户地区选支付方式:
strategyFactory.getStrategy(country) - 需要叠加通知?用
CompositeNotifier聚合 Email + SMS,自身不继承任何 notifier - DI 容器按环境注入:开发用内存队列,生产用 Kafka,零修改业务类
不再为每种组合提前写一堆子类,扩展成本从“写类+改调用”降到“写一个组件+配一下”。
守住生命周期和边界底线
组合不是随便 new 一堆对象往类里塞。要分清:
- 组合:生命周期一致,如订单包含订单项 → 在构造函数中创建
- 聚合:生命周期独立,如用户持有邮箱服务 → 由容器或工厂注入
- 避免循环引用:A 持有 B,B 又持有 A,会导致初始化失败或内存泄漏
- 资源型对象(DB 连接、HTTP 客户端)统一交由 DI 容器管理,业务类不自行 new
不复杂但容易忽略。











