单一职责原则:一件事只做一件事

单一职责是软件组织的第一原则:一个东西只做一件事。 它不是某个粒度的规则,而是贯穿所有粒度的主题——函数、类、文件、模块、组件、系统,每一层都在执行同一个原则。职责混乱就拆分,拆到「每个单元只承担一个职责」为止。


一、原则一句话

一个东西只做一件事。

更准确地说:

职责单一,按变化原因拆分,而不是单纯因为数量多就拆。

1
2
❌ 一个函数 100 行但只做一件事 → 不该拆(职责单一)
✅ 一个函数 20 行但做三件事 → 该拆(职责不单一)

判断标准是职责,不是大小。


二、单一职责贯穿所有粒度

1
2
3
4
5
6
7
8
函数      只做一个操作(一个明确操作)
类 只封装一个对象(对象/状态/行为的封装)
文件 只承担一个职责(编译单元)
模块 只负责一个功能(独立功能单元)
组件 只提供一个功能单元(数据+算法+接口)
子系统 只负责一类相关功能(一组相关功能)
分层 每层只处理一类问题(界面/业务/基础设施)
原子化接口 只做封装库接口 + 异常处理
1
2
3
MovementSystem 只负责移动,不负责碰撞、不负责渲染
ProcessService 只负责进程业务,不负责网络、不负责 UI
Parser 只负责字节↔消息转换,不负责路由分发

三、为什么要单一职责

一个东西做了太多事,代价:

1
2
3
4
❌ 改一个职责影响其他职责(牵一发动全身)
❌ 无法独立测试(测试 A 职责必须带上 B 职责)
❌ 无法独立复用(想复用 A 职责,被迫带上 B 职责)
❌ 理解困难(一个东西要同时理解好几件事)

单一职责后:

1
2
3
4
✅ 改 A 不影响 B(职责独立)
✅ 单独测试 A(职责独立)
✅ 单独复用 A(职责独立)
✅ 理解 A 只需理解一件事

单一职责降低的是「复杂度和耦合」——每个单元只承担一个职责,所以改、测、复用、理解都只需要面对一件事。


四、单一职责 vs 拆分

单一职责和拆解是同一个动作的两面:

1
2
3
4
5
职责混乱 → 拆分(沿边界切)
拆分到什么时候停?→ 每个单元职责单一

拆分依据 = 职责(一件事 vs 多件事)
停止条件 = 单一职责(每个单元只做一件事)
1
2
3
4
5
6
main 膨胀(职责混乱)
→ 拆函数(每个函数一个职责)
→ 拆文件(每个文件一个职责)
→ 分层(每层一个职责)
→ 拆模块(每个模块一个职责)
→ 拆系统(每个系统一个职责)

拆解是动作,单一职责是停止条件。 拆到每个单元职责单一,就是拆分完成。


五、单一职责 vs 重复

软件组织的两条第一原则:

1
2
3
4
5
职责混乱 → 拆分(单一职责)
重复代码 → 封装(复用)

一个东西做了太多事 → 拆分(降低复杂度)
很多地方做同一件事 → 封装(降低重复)
1
2
一个 PlayerSystem 处理输入+移动+碰撞+渲染 → 职责问题 → 拆成多个 System
十几个地方写相同文件读取 → 重复问题 → 封装成 FileService

职责混乱就拆分(单一职责),重复代码就封装(复用)。 这是软件组织的两条第一原则,单一职责是其中之一。


六、和其他内容的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
单一职责 ←→ 拆解与组织
拆解沿边界切,单一职责是停止条件

单一职责 ←→ 边界
职责边界 = 单一职责的直接体现(边界五种之一)

单一职责 ←→ 原子化接口
原子化接口 = 单一职责在接口层的实现

单一职责 ←→ 从指令到系统主线
主线每一步都是职责混乱 → 拆分
拆分的目标就是单一职责

单一职责 ←→ 功能函数与模块
功能函数 = 数据结构 + 算法 + 接口(各自单一职责)
模块 = 一个功能的组合

收束

1
2
3
4
5
6
7
8
9
10
11
12
13
单一职责 = 一个东西只做一件事
判断标准是职责,不是大小

贯穿所有粒度:函数/类/文件/模块/组件/系统/分层/接口

代价:改 A 影响 B / 无法独立测试复用 / 理解困难

单一职责是拆解的停止条件:
拆分依据 = 职责,停止 = 每个单元职责单一

两条第一原则:
职责混乱 → 拆分(单一职责)
重复代码 → 封装(复用)

一件事只做一件事。 这是软件组织的第一原则——从最小的函数到最大的系统,都在执行它。