优先选组合而非继承,因组合更灵活、可测试性好、语义清晰;继承仅适用于满足“is-a”关系且父类明确为继承设计的情形,如abstractlist;误用如dog extends databaseconnection违背语义,属典型坏味道。

优先选组合,慎用继承。继承容易导致紧耦合、破坏封装,而组合更灵活、可测试性更好、语义更清晰。
什么时候该用继承
仅当满足“is-a”关系,且父类是为继承而设计的(比如明确标注了protected接口、有文档说明可被子类重写、提供template method等钩子)。
- Java 的
AbstractList是为继承设计的,可以安全扩展 - 但
ArrayList不是——它没承诺任何继承契约,直接继承它属于误用 - 如果只是想复用逻辑,却强行让
Dog extends DatabaseConnection,就违背了语义,是典型坏味道
组合怎么写才真正有效
组合不是简单地在类里 new 一个对象,关键在于:通过接口依赖、控制生命周期、暴露必要能力。
- 依赖抽象而非具体类型:用
PaymentProcessor接口,而不是PayPalProcessor类 - 外部可替换:测试时能轻松注入 mock,生产时可切换 Stripe 实现
- 不暴露内部细节:比如
OrderService持有NotificationSender,但不把 sender 的 connect() 或 retry() 方法透给上层调用
继承的常见陷阱
看似省代码,实则埋雷:
- 脆弱基类问题:父类一改,所有子类行为可能意外变化(比如加了个新 protected 方法,恰好和子类同名,引发覆盖或编译错误)
- 构造顺序陷阱:子类构造器中调用被重写的方法,此时父类字段还没初始化完毕,易出 NullPointerException
- 无法多重复用:Java 不支持多继承,而一个类常需多种能力(如可缓存 + 可重试 + 可熔断),组合天然支持横向叠加
一个实用判断流程
遇到复用需求时,快速自问三句:
- 它真的是“是一种”关系吗?(不是“有一个”或“用一下”)
- 父类是否公开声明支持继承?有没有配套的 hook 和文档?
- 如果明天要换掉这个能力(比如短信通知换成邮件),继承方案要改几处?组合方案改几处?
多数时候,第三问的答案会告诉你:组合更轻、更稳、更可持续。










