组合优于继承是可操作的设计习惯:复用功能优先组合,仅当明确“是一种”且父类支持时才用继承;用private final+构造器注入替代继承,将变化点抽为策略接口并运行时注入,重构继承树时转为has-a关系并委托,警惕框架强制继承中的业务逻辑隔离。

组合优于继承不是一句口号,而是可操作的设计习惯。核心是把“复用功能”和“表达关系”分开处理:需要复用时优先组合,只有明确成立“是一种”且父类支持继承时才考虑 extends。
用 private final + 构造器注入替代继承
这是最稳妥的落地方式。比如你想复用日志能力,别让 Service 继承 Logger 类(它根本不是一种日志),而是:
- 定义一个 Logger 接口或具体类(如 Slf4jLogger)
- 在业务类中声明 private final Logger logger
- 通过构造器传入,用 Objects.requireNonNull() 校验非空
- 所有日志调用都走 logger.info(),不暴露、不继承、不误用 protected 方法
把变化点抽成策略接口,运行时注入
当行为随类型切换(如支付、通知、校验),别靠子类重写方法,而用组合+策略:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 定义 PaymentStrategy 接口,含 pay(Order order)
- 实现 AlipayStrategy、WechatStrategy 等具体类
- OrderService 持有 private final PaymentStrategy strategy
- 测试时可直接传入 MockStrategy,生产可按配置切换实现
重构老式继承树:从 is-a 到 has-a 再委托
遇到类似 “A extends B” 但只用到 B 的 20% 功能时,说明继承已失焦:
立即学习“Java免费学习笔记(深入)”;
- 先识别真正被依赖的行为(比如只用了 B 的 save() 和 findById())
- 提取出 Persistence 接口,把这两个方法挪进去
- A 改为持有 private final Persistence persistence
- 原来调 super.save() 的地方,改为调用 persistence.save()
- 底层实现可随时从 JdbcPersistence 换成 RedisPersistence,不影响 A
警惕框架强制继承场景下的隔离设计
Spring 的 @Controller、MyBatis 的 Mapper 接口虽要求继承或实现,但业务逻辑不该和框架耦合:
- 避免在 Controller 里写数据库操作或复杂校验
- 用组合引入 Service 层对象,Controller 只做参数接收和响应包装
- 若团队封装了 BaseController,可在其上加一层适配器类,把框架相关逻辑(如权限检查)和业务逻辑隔开
- 这样即使某天换掉框架,只需替换适配器,核心业务代码不动

















