单一职责原则要求每个类只有一个变化原因。判断依据是需求变更时是否需修改同一类;拆分应按变化维度而非名词,配合依赖注入解耦;方法和模块同样适用该原则。

在 Java 面向对象编程中贯彻单一职责原则(SRP),核心是让每个类只承担一个明确的、可独立变化的职责。不是“功能越少越好”,而是“变化原因只有一个”——这个变化原因,就是你未来可能要修改它的唯一理由。
识别职责边界:从“做什么”转向“为什么改”
判断一个类是否违反 SRP,关键不在于它有几行代码或几个方法,而在于:如果需求变了,比如日志格式调整、数据库换 MySQL 为 PostgreSQL、导出格式从 Excel 改成 PDF——这些改动,是否都得动同一个类?如果是,说明职责混杂了。
- 把“用户保存”和“用户导出”放一起 → 两个变化原因(业务逻辑改 vs 报表需求变)→ 违反 SRP
- 把“邮箱校验”和“发送邮件”写在 User 类里 → 两个变化原因(规则升级 vs 邮件服务商切换)→ 违反 SRP
- 把“连接数据库”“查客户”“画图表”全塞进 CustomerDataChart → 三个变化原因(DB 配置、查询条件、UI 展示)→ 明显违反
拆分策略:按变化维度切分,不是按名词切分
拆分不是简单按“User”“Log”“Excel”起名就完事,而是看谁会因为什么而改。常见合理拆分方式:
- 数据模型类(User):只存字段、getter/setter、基础校验(如邮箱格式),不碰 I/O 或业务流程
- 数据访问类(UserRepository):只管增删改查,用 JDBC、MyBatis 或 JPA 封装,换数据库只需改它
- 业务服务类(UserService):编排逻辑(如注册时先校验再保存再发通知),但不实现校验或发邮件细节
- 辅助能力类(EmailService、LogService):各自专注一件事,可被多个业务复用
依赖注入代替硬编码,让职责真正解耦
拆完类只是第一步;若 UserService 里 new EmailService(),那它仍“知道”怎么发邮件,职责没真正分离。正确做法是通过构造器或 setter 注入依赖:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
✅ 正确示例:
public class UserService {<br> private final UserRepository repo;<br> private final EmailService emailService;<br><br> public UserService(UserRepository repo, EmailService emailService) {<br> this.repo = repo;<br> this.emailService = emailService;<br> }<br><br> public void register(User user) {<br> repo.save(user);<br> emailService.sendWelcome(user.getEmail());<br> }<br>}
这样,UserService 只负责“注册流程”的编排逻辑,不关心邮件怎么发、数据库怎么连——它的唯一变化原因是注册流程本身调整。
从小处开始:方法和模块也适用 SRP
单一职责不限于类。一个方法如果做了校验 + 保存 + 发消息 + 记日志,它同样违背 SRP。建议:
- 方法命名体现单一意图,如
validateEmailFormat()、sendVerificationEmail() - 模块层面,避免“Utils”“Helper”这种大杂烩包;按领域建包,如
com.example.user.repo、com.example.user.notification - 测试更轻松:一个类职责单一,单元测试用例聚焦、Mock 简单、失败定位快


















