应优先用组合替换继承,即按需拆解能力为可插拔组件;识别滥用继承的三类信号:子类大量重写却只调用部分父逻辑、子类对父功能无感却无法删除、新增变体被迫加继承层但无“是一个”关系;将能力定义为接口+实现,如PriceCalculator等,按场景注入;通过构造函数或setter传入组件以支持测试与动态切换;仅对真正共享的骨架行为(如日志、事务模板)保留抽象基类,业务逻辑全部下沉至组合组件。

直接用组合替换继承,不是简单地把 extends 换成字段引用,而是重新思考“这个类到底需要什么能力”,再把能力拆成可插拔的组件。
先识别哪些地方真正在滥用继承
重点看三类信号:子类大量重写父类方法却只调用 super 的一部分逻辑;子类对父类某功能完全无感,但又无法删掉(比如订单类继承了支付校验,而某些内部订单根本不用支付);新增一个业务变体时,不得不在继承链上加一层,但新类和已有类之间其实没有清晰的“是一个”关系。这些都不是小问题,是业务模型和代码结构开始脱节的征兆。
把能力拆成接口+实现,而不是类+子类
比如原有一个 OrderService 父类,里面混着价格计算、库存检查、通知发送三块逻辑。不要让 VIP 订单和秒杀订单都去继承它,而是定义三个接口:PriceCalculator、InventoryChecker、Notifier。每个具体业务场景按需选用实现类——VIP 订单配 VIPPriceCalculator,秒杀订单配 FlashSaleInventoryChecker,普通订单用默认实现。Spring Boot 中直接用 @Autowired 注入对应 Bean 即可。
用构造函数或 setter 控制组合时机
避免在类内部硬编码 new 实例。把组件作为构造参数传入,既方便单元测试(可传 mock),也支持运行时切换。例如:
- 创建订单服务时,传入不同的 PriceCalculator 实现
- 测试环境下注入 StubInventoryChecker,跳过真实库存调用
- 灰度发布时,按用户 ID 哈希动态选择新旧 Notifier
保留继承仅用于真正共享的骨架行为
不是彻底禁用继承。如果多个类确实共用一套不可变的流程框架(比如统一的日志埋点、异常包装、事务模板),可以保留一个抽象基类,但里面只放模板方法和钩子,不放具体业务逻辑。真正的业务判断全部下沉到组合进来的策略组件里。这样父类稳定,子类灵活,各司其职。










