组合是默认首选,继承仅适用于明确稳定的“是一个”关系且符合liskov替换原则;需确认父类专为继承设计,否则优先用接口+组合实现复用、替换与解耦。

直接看关系本质,而不是复用方便与否。组合是默认首选,继承只在极少数明确、稳定、符合设计契约的场景下才用。
先判断是不是“是一个”关系
继承表达的是 is-a,比如 Dog 是 Animal,ArrayList 是 List 的一种实现。这种关系必须自然、稳定、可被外部验证——子类对象能安全替换父类对象(Liskov 替换原则成立)。
如果只是“能用某个功能”,比如“订单需要支付”,那不是“订单是一种支付方式”,而是“订单有一个支付处理器”,这就该用组合。
检查父类是否专为继承而设计
不是所有公开类都欢迎被继承。真正适合继承的父类通常具备这些特征:
- 文档中明确说明“可被子类扩展”,或有
@MustOverride等标记 - 提供
protected钩子方法(如onInit()、doProcess())供子类定制 - 构造逻辑清晰,不依赖未初始化的子类状态
- 没有在构造器中调用可被重写的方法(否则易出
NullPointerException)
像 String、LocalDateTime 这类 final 类,或 JDK 中大量使用 private 方法封装的类,本质上已关闭继承通道,强行继承会失败或反模式。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
优先用组合实现行为复用和可变逻辑
当目标是复用功能、切换策略、解耦依赖或便于测试时,组合天然更合适:
- 把可变部分抽成接口(如
PaymentStrategy),通过构造函数或 setter 注入 - 运行时可替换实现(比如测试时注入
MockPaymentStrategy) - 避免父类修改导致所有子类连锁失效(脆弱基类问题)
- 天然支持 Spring 等框架的依赖注入,生命周期可控
例如:支付模块不用为微信、支付宝、银联各写一个继承自 BasePayment 的子类,而应让 OrderService 持有 PaymentStrategy 接口,按需注入具体实现。
框架强制要求时,继承不可替代但可隔离
某些场景继承是契约而非选择:
- Spring MVC 的
@Controller类若统一继承了BaseController(含权限校验、日志模板) - JUnit 4 测试类需继承
TestCase - Servlet 必须继承
HttpServlet
这时可在继承链顶端加一层适配器(Adapter),把业务逻辑封装进组合组件中,让继承层只负责对接框架,不掺杂核心逻辑。这样既满足框架约束,又保留组合的灵活性和可测性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










