组合代替继承是为将“我是一个什么”转为“我拥有什么”,通过接口定义可插拔能力(如paymentstrategy)、构造注入和委托调用,实现运行时策略切换与低耦合。

在复杂业务系统中,用组合代替继承不是为了否定继承,而是把“我是一个什么”换成“我拥有什么”,让变化的部分可插拔、可替换、可测试。核心目标是控制耦合范围,避免改一个类牵动一整条继承链。
识别哪些功能该从继承里拆出来
先看现有代码里反复出现但实现各异的能力:日志记录、缓存读写、支付处理、通知发送、重试策略、数据校验、序列化格式等。这些不是业务实体的固有属性,而是横切或可选能力。它们一旦被塞进基类,就会导致子类被迫继承不相关行为,也容易引发脆弱基类问题。
- 比如 OrderService 继承 BaseService,结果所有子类都带上了一套固定日志格式和数据库连接方式——其实不同订单类型可能需要打到不同日志系统
- 再比如 PaymentProcessor 继承 AbstractPayment,但新需求要支持离线重试 + 微信回调 + 邮件通知,每加一个维度就得新建子类,很快爆炸
- 真正该保留在继承里的,只有语义清晰、长期稳定、不可替代的 is-a 关系,如 AdminUser 是 User,ApiResponse 是通用响应载体
用接口定义能力边界,小而专
把每个可变行为抽象成单一职责接口,命名体现业务意图,而不是技术动作:
- PaymentStrategy:只含 charge() 和 refund(),不暴露签名算法或渠道配置
- NotificationChannel:只含 send(message, recipient),不暴露短信网关或邮件模板引擎细节
- RetryPolicy:只含 shouldRetry(exception) 和 nextDelay(),不暴露指数退避参数或最大次数
主业务类(如 OrderService)只持这些接口的引用,不关心具体是谁实现。新增微信支付?写个 WechatPaymentStrategy 实现 PaymentStrategy 即可,主逻辑零改动。
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
构造注入 + 委托调用,保证对象可用性
依赖必须通过构造器传入,字段声明为 private final,并用 Objects.requireNonNull() 校验。这能防止空指针,也确保对象一创建就处于完整状态。
- 别在类内部 new RetryPolicy() 或 new JsonSerializer(),否则无法替换、无法 mock
- 对外只暴露聚合后的行为,比如 processOrder(),不暴露 getPaymentStrategy() 这类 getter;更不能返回内部对象引用,否则封装就失效了
- 委托不是简单转发,而是做必要适配:比如把 Order 对象转成 PaymentRequest 再交给 PaymentStrategy 处理
运行时装配与策略切换
组合的价值在运行时才真正释放。你可以根据上下文动态换组件,而不用编译期就决定好所有组合分支:
- 下单时根据用户国家选支付策略:
payment = strategyFactory.getStrategy(user.getCountry()) - 灰度发布时,对 5% 的请求注入 MockPaymentStrategy,其余走真实渠道
- 测试时直接传入 StubNotificationChannel,完全绕过短信网关和邮件服务器
这种灵活性是继承做不到的——你没法在运行时把一个已创建的子类“切换成另一个父类”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










