单一职责原则(SRP):一个类只有一个变化原因
更新时间:2026-08-25。本文从语义与意义两个层面论述单一职责原则(Single Responsibility Principle, SRP):它到底在约束什么、为什么它是所有设计模式的前置约束。
一、语义:SRP 到底在说什么
SRP 的经典表述:一个类应该只有一个引起它变化的原因(A class should have only one reason to change)。
拆开这句话,三个关键词决定了它的全部含义:
| 关键词 | 含义 | 常见误解 |
|---|---|---|
| 一个类 | 对象粒度上的职责边界 | 误解为"一个方法只干一件事"(那是函数级内聚) |
| 只有一个 | 职责的数量边界 | 误解为"一个类只能有一个方法" |
| 变化的原因 | 职责的本质是"变更来源",不是"功能数量" | 误解为"职责=功能",功能多不等于职责多 |
语义要点:职责不是"这个类做了几件事",而是"有几种不同的力量会让这个类被修改"。一个类做了 10 件事,但如果这 10 件事永远一起变化(例如一个账务类的所有字段和算法都属于"会计核算规则"这一个领域),它依然只有一个职责;反之,一个类只做了 3 件事,但这 3 件事分属"业务规则、持久化、展示"三个领域,它就有 3 个职责。
职责的识别锚点:问"谁会要求我改这个类?"——产品经理(业务规则)、DBA(存储结构)、前端同事(展示格式)……每一个不同的"要求来源"就是一个独立职责。
二、意义:为什么必须守住
SRP 的意义不在"整洁",而在风险管理:
- 隔离变更爆炸:多个职责挤在一个类里,任一职责的变更都要"碰"这个类,而类里还有其他不相关的逻辑——改一处、伤一片的回归风险随职责数量线性上升。
- 可测试性:测 A 职责时要构造 B、C 职责的依赖(mock 数据库、mock 网络),测试成本高到团队不愿写测试。
- 可替换性:职责混杂的类无法被单独替换——想换存储引擎,却要连带着接受它内部的日志、统计逻辑。
- 并发与性能(本仓库主线视角):一个类同时管"业务计算"和"锁",职责间互相拖累,锁的粒度被迫放大,热点更难优化。
三、违反与守好:对比类图
违反 SRP 的样子——一个类兼任数据、校验、展示、日志:

守住 SRP 的样子——每个职责一个类,协作而非纠缠:

对比分析
| 维度 | 违反 SRP(上帝类) | 守住 SRP(职责拆分) |
|---|---|---|
| 变更范围 | 任一职责变 → 改 Order 一个类 | 各职责各改各的类,互不波及 |
| 回归风险 | 高(改动区域与其他职责重叠) | 低(改动被类边界隔离) |
| 测试 | 需 mock 全部依赖,成本极高 | 每个类可独立单测 |
| 可替换 | 换存储 = 连带动其他逻辑 | 换 OrderRepository 即可 |
| 性能优化 | 热点被锁/日志等无关逻辑拖累 | 计算类可单独优化、单独加锁 |
四、如何判断:实践检查清单
| 症状 | 判断 |
|---|---|
| 类超过 ~200 行且方法主题分散 | 高度可疑,先问"几个变化来源" |
方法名跨越多个领域动词(saveToDB、renderHTML、logOrder) | 已违反,按领域拆分 |
| 测试文件里大量 mock 与本类无关的依赖 | 违反,职责在拽着无关依赖 |
| 改"日志格式"要重新测"业务计算" | 违反,两个职责耦合在同一类 |
| 新需求永远只改这一个文件 | 违反,该类成了"需求垃圾桶" |
注意边界:SRP 不要求"类越小越好"。一个类如果只有一个领域、一个变化来源,哪怕 300 行也是合理的内聚;判断标准始终是"变化来源的数量",不是行数。
五、与设计模式的关系
SRP 是所有设计模式的前置约束——它不直接对应某个模式,但没有它任何模式都落不了地:
| 体现方式 | 模式示例 |
|---|---|
| 把"附加能力"拆成独立类 | Decorator(每个装饰器只加一层职责) |
| 把"子系统协调"独立成门面 | Facade(协调职责从客户端剥离) |
| 把"对象创建"独立成类 | Factory Method / Abstract Factory(创建职责与使用职责分离) |
| 把"算法"独立成策略对象 | Strategy(算法职责与上下文分离) |
| 把"状态迁移"独立成状态类 | State(每个状态一个类,职责单一) |
一句话总结
单一职责原则约束的是"变化来源的数量"而非"功能数量"——每个类只为一个角色负责;守住它,变更被类边界隔离、测试可独立进行,这也是其他一切设计模式能够成立的前提。