组合替代继承的核心是将“我是一个什么”变为“我拥有什么”,通过拆分可复用行为、封装专注小类、构造器注入依赖、委托暴露接口、支持策略切换与测试隔离,实现松耦合与高内聚。

直接用组合替代继承,核心是把“我是一个什么”变成“我拥有什么”,从强绑定转向松耦合。不是删掉父类,而是把原本靠继承获得的能力,改由独立对象提供。
识别并拆分可复用的行为
先看现有继承链里哪些方法或字段真正被子类共用,且不随具体类型变化。比如多个类都需日志能力,但日志方式(控制台、文件、网络)不同——这就不该放在父类里硬编码,而应抽成 Logger 接口和若干实现类。
- 把原父类中与核心身份无关的功能(如序列化、校验、缓存、通知)逐个拎出来,封装为小而专注的类
- 每个新类只做一件事,比如 JsonSerializer 只管转 JSON,RetryPolicy 只管重试逻辑
- 避免出现“工具类大杂烩”,一个类里塞了十种功能
用 private final 字段 + 构造器注入替代 extends
原来写 class OrderProcessor extends BaseService,现在改为 class OrderProcessor,内部持有一个 ServiceHelper 实例。关键不是加个字段就完事,而是控制权转移。
- 声明 private final 字段持有依赖,构造器注入,避免 null 和后期篡改
- 构造函数参数务必用
Objects.requireNonNull()校验,失败早于业务逻辑 - 别在类里直接写
new Engine():无法替换、无法 mock、生命周期失控 - 别提供
setEngine()除非真需要运行时热切换(比如灰度切换支付渠道)
通过委托暴露行为,而非透传或暴露底层
把原来直接调用的 getName()、getRole() 等方法,改成 return user.getName(); —— 这叫委托(delegation),不是简单转发。
立即学习“Java免费学习笔记(深入)”;
- 对外只暴露你需要的能力,不泄漏底层实现(如 Stack 不该暴露
add()、get()等 List 接口) - 别偷懒写
public User getUser() { return user; },这等于把封装捅了个洞 - 依赖尽量声明为接口(如 DataSource 而非 MySQLDataSource),方便替换
- 避免在组合链上深度调用(如
a.getB().getC().doX()),这会把空指针风险和耦合层层传递
支持运行时策略切换与测试隔离
继承中常靠 override 改变父类方法,但一旦父类逻辑调整,所有子类都要跟着动。组合下,你可以随时换掉整个协作对象。
- 支付类不再继承 AlipayPayment 或 WechatPayment,而是持有一个 PaymentStrategy 接口实例
- 下单时根据用户地区动态选策略:
self.payment = strategy_factory.get_strategy(country) - 测试时只需 mock 掉依赖对象,不用搭起整个类层次
- 旧代码可继续用继承版,新模块统一走组合接口,逐步迁移


















