组合优于继承是应落地的设计习惯:先判断类关系是“是一种”还是“有一个”,属后者则用组合;字段私有不可变、构造器注入、禁用setter和getter;委托需预留校验、记录、异常处理等扩展点;配合接口实现策略可插拔。

组合优于继承不是一句口号,而是在业务代码里能立刻落地的设计习惯。核心就一条:别为了复用代码就去 extends,先问清楚——这两个类之间,到底是“是一种”,还是“有一个”。
看关系,不是看功能相似性
很多开发者一看到“订单要付款”,就让 Order 继承 PaymentService,这不对。Order 不是 PaymentService 的一种,它只是“有一个”支付能力。类似地:
- 用户和地址:User 有 Address,不是 User 是一种 Address
- 商品详情页和评论模块:DetailPage 有 CommentSection,不是 DetailPage 是一种 CommentSection
- 报表导出和 ExcelWriter:ReportExporter 有 ExcelWriter,不是 ReportExporter 是一种 ExcelWriter
凡是“有”的地方,就该用组合;强行继承,后续加日志、换实现、做灰度,全都会卡在父类的接口和生命周期里。
组合要写得安全、可控、可测
光把对象塞进字段里不算完,关键是怎么持、怎么用:
- 字段声明为 private final,比如
private final PaymentProcessor processor; - 依赖通过构造器注入,且用
Objects.requireNonNull()校验非空,避免运行时 NPE - 不暴露 setter 方法,除非真需要运行时切换(如按渠道动态换微信/支付宝策略)
- 不提供 public getter,防止外部绕过你的业务逻辑直接调用底层方法
这样写,单元测试时可以直接传入 MockPaymentProcessor,Spring 启动时也能靠 Bean 注入自动装配,不会被 new 出来的硬编码锁死。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
委托方法要留扩展点,不能简单 return
组合不是“甩手掌柜”。比如 OrderService 调用支付,别只写 return processor.pay(order);。中间要能插逻辑:
- 调用前校验订单状态、风控规则、余额是否充足
- 调用后记录流水、更新订单状态、触发消息通知
- 失败时统一包装成
BusinessException,而不是把 SDK 的WechatApiException直接往上抛
每一处委托,都是你掌控行为的入口。继承做不到这点——子类重写方法容易漏掉父类的隐式逻辑,而组合让你每一步都看得见、改得了。
用接口解耦,让策略可插拔
组合 + 接口,才是应对多变业务的正解。例如定义:
interface DeliveryStrategy { void deliver(Order order); }class SFExpressStrategy implements DeliveryStrategy { ... }class CainiaoStrategy implements DeliveryStrategy { ... }
业务类只依赖 DeliveryStrategy 接口,具体用哪家物流,由 Spring Profile、配置中心或订单属性(如 order.getDeliveryType() == "SF")决定。未来加京东物流、自建车队,只需新增实现类,完全不碰原有业务逻辑。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










