组合优先于继承是为避免耦合重、维护难、测试脆而确立的设计纪律;关键在权衡风险,语义上“有一个”用组合,“是一个”才考虑继承,并需验证父类是否真正支持继承。

组合优先于继承不是一句空话,而是为避免耦合过重、维护困难、测试脆弱等实际问题总结出的设计纪律。关键不在“能不能用继承”,而在“值不值得冒风险用”。
看语义关系:是“有一个”还是“是一个”
直接读类名判断最有效:
- Dog 是 Animal → 继承合理
- Car 有 Engine → 必须组合,不能让 Car extends Engine
- Order 有 PaymentStrategy 和 NotificationChannel → 多个组合对象协同,继承无法表达这种横向依赖
检查父类是否真正支持被继承
即使语义成立,也要停顿三秒问自己:
- 父类有没有声明为
abstract?文档里有没有写“Designed for inheritance”? - 父类有没有大量
protected字段或未封装的内部逻辑?子类一改就崩,说明它本就不该被扩展 - 子类重写方法时,是否必须调用
super.xxx()才能保日志、权限、事务?这是实现细节泄漏,不是设计契约
用委托代替继承,控制行为边界
组合不是简单加个字段,核心是委托(delegation)——把动作转给另一个对象执行:
立即学习“Java免费学习笔记(深入)”;
- 不要写
class Stack extends Vector(JDK 早期反例),而应持有一个List,只暴露push()和pop(),内部调用list.add()和list.remove() - 接口粒度要准:PaymentProcessor 只需暴露
process()和refund(),不把策略内部的validate()或format()暴露出来 - 构造时注入依赖,避免 setter 注入导致对象半初始化;若字段是
Closeable(如BufferedReader),记得在本类close()中显式关闭
继承只在明确安全的场景才启用
它没被淘汰,但适用范围很窄:
- 框架集成:Spring MVC 中统一继承
BaseController做模板逻辑,属于框架约定,绕不开 - 纯权限/属性扩展:AdminUser extends User,不覆盖行为,只增加字段和访问控制
- 模板方法模式:AbstractService 定义
execute()骨架,子类只填doExecute(),流程控制权仍在父类


















