单一职责原则(SRP)要求类的所有行为围绕同一明确业务责任展开,高内聚体现为属性与方法语义紧密协同;Java中需识别责任边界、封装变化点、用接口隔离角色,并以测试脆弱性为重构信号。

单一职责原则(SRP)不是“一个类只做一件事”的简单口号,而是要求一个类的所有行为都围绕同一个、明确的业务责任展开。高内聚的本质,是让类内部的属性和方法在语义上紧密相关、协同服务于同一目标。在Java中落地SRP,关键不在于拆分数量,而在于识别责任边界、封装变化点、并用清晰的命名与接口表达意图。
从“动词”出发,识别真实的责任主体
很多类职责混乱,是因为把操作当成了责任。例如写一个OrderProcessor类,里面包含校验订单、计算折扣、生成发票、发送邮件——这些是四个动作,但未必属于同一责任。应反问:谁“拥有”这个逻辑?谁对该逻辑的正确性、变更频率、失败后果负最终责任?
- 订单校验 → 属于OrderValidationPolicy(策略可替换,规则常变)
- 折扣计算 → 属于DiscountCalculator(可能按会员等级/活动类型多态)
- 发票生成 → 属于InvoiceGenerator(涉及模板、PDF渲染等独立技术栈)
- 邮件通知 → 属于NotificationService(需对接SMTP、重试、日志审计)
每个类只封装一类变化原因,接口名体现其契约(如isValid()、calculateFor(Order)),而非流程步骤(如doStep3())。
用接口隔离职责,避免“伪高内聚”
即使一个类只做“一件事”,若它同时持有数据、处理逻辑、访问外部资源,仍属低内聚。应通过接口将角色分离:
立即学习“Java免费学习笔记(深入)”;
- 定义OrderRepository接口,由具体实现(JDBC/JPA)负责持久化细节
- 定义Clock接口,屏蔽系统时间获取方式(便于测试模拟)
- 定义EventPublisher接口,解耦领域事件发布机制
主业务类(如OrderFulfillmentService)只依赖这些抽象,自身专注编排逻辑:调用校验、调用计算、调用生成、触发通知——它不“做”这些事,只是协调“谁来做”。这样,每个类的修改动机彼此独立。
警惕“上帝类”的隐性变体:贫血模型+服务层大杂烩
常见误区是把实体类设计成纯数据容器(Order只有getter/setter),所有逻辑堆在OrderService里。这看似遵守了SRP(“Service就该处理业务逻辑”),实则把多个责任强行塞进一个类。
更合理的做法是:
-
Order自身封装不变量和核心行为(如
confirm()检查状态合法性、addLineItem()维护总价一致性) - 跨聚合或外部协作逻辑下沉为专用服务(如InventoryReservationService负责扣减库存)
- 复杂业务流程用领域服务(OrderFulfillmentOrchestrator)组合,但不包含具体算法
此时Order的内聚体现在:它的字段、构造约束、状态迁移方法,全部服务于“订单”这一领域概念的完整性。
重构时以“测试脆弱性”为信号,验证职责是否纯净
当一个单元测试频繁因无关改动而失败,往往说明类承担了不该承担的责任。例如:
- 改了邮件模板,OrderServiceTest却报错 → 邮件逻辑不该在OrderService里
- 加了个新折扣类型,必须同时修改校验、计算、日志三处代码 → 折扣策略未被抽象
- 数据库字段变更导致10个测试失败 → 实体类与DAO耦合过紧
每次测试失败,都应追问:“这个变化,是否本该只影响一个类?” 若答案是否定的,就是SRP被破坏的明确信号,也是重构的起点。


















