实际开发中应优先用组合代替继承,即将“我是一个什么”转为“我拥有什么”,通过接口+小类解耦日志、缓存等复用逻辑,用构造器注入依赖并委托行为,支持策略切换与测试隔离。

实际开发中优先用组合代替继承,关键不是删掉父类,而是把“我是一个什么”转成“我拥有什么”,让类之间从强绑定变成松耦合。重点不在加字段,而在转移控制权——谁创建、谁管理、谁决定用哪个实现。
识别该拆的复用逻辑
先看现有继承链里哪些能力其实和类的核心身份无关:日志、缓存、序列化、重试、通知等。这些功能往往在多个类里重复出现,但具体实现方式各异(比如日志可写到文件、数据库或远程服务)。它们不该硬塞进父类,而应抽成接口+小类:
- 定义 Logger 接口,再写 FileLogger、RemoteLogger 等实现
- 把 JsonSerializer、XmlSerializer 分开,每个只做一种格式转换
- 避免出现“工具类大杂烩”,一个类里别同时管校验、缓存、加密
用构造器注入替代 extends
原来 class OrderService extends BaseService,现在改成 class OrderService,内部持有一个 private final ServiceHelper helper:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 字段声明为 private final,确保不可变、不为空
- 构造器接收依赖,用 Objects.requireNonNull() 校验入参
- 别在类里直接 new 实例(如 new RetryPolicy()),否则无法替换、无法 mock
- 除非真要运行时切换(比如灰度发版换支付渠道),否则别暴露 setXXX() 方法
通过委托暴露行为,不透传底层
原来直接调 user.getName(),现在在 OrderService 里写 return user.getName();——这是委托,不是转发:
- 对外只暴露需要的能力,比如 processOrder()、cancel(),不暴露 getUser() 这种 getter
- 别返回内部对象引用(如 public User getUser() { return user; }),这等于把封装捅漏了
- 依赖尽量声明为接口(如 DataSource 而非 MySQLDataSource),方便后续替换成 Redis 或 Mock 实现
- 避免深度调用链,比如 a.getB().getC().doX(),容易空指针且耦合层层传递
支持运行时策略切换与测试隔离
继承靠 override 改行为,但父类一动,所有子类都得跟着测;组合靠接口注入,随时可换协作对象:
- 支付模块不再继承 AlipayPayment 或 WechatPayment,而是持有 PaymentStrategy 接口实例
- 下单时根据用户地区动态选策略:self.payment = strategyFactory.getStrategy(country)
- 单元测试时,直接注入 MockPaymentStrategy,不用搭起整个继承树
- 旧代码可继续用继承版,新模块统一走组合接口,逐步迁移不伤筋动骨
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










