一个类(或模块、函数),应该有且仅有一个引起它变化的原因。

核心概念:什么是“单一职责”?

通俗来说,一个类只用心做好一件事。
打个生活中的比方:

  • 不符合原则的设计(多功能军刀): 假设你买了一把集成了“剪刀、厨师刀、指甲刀、开瓶器、锯子”的万用军刀。某一天,如果你觉得剪刀部分太钝,想把它拆下来磨一磨,很有可能会把旁边的开瓶器弹簧弄坏,或者导致整把刀松动无法使用。

  • 符合原则的设计(专业工具): 厨房菜刀只负责切菜,修剪指甲只用指甲刀。菜刀坏了,拿去磨或者换掉,完全不会影响你剪指甲。

在代码世界里,“引起变化的因素”通常对应着不同的角色或业务需求(比如财务部门提出了计税逻辑修改、运维部门提出了数据库结构修改)。如果把这两块逻辑塞进同一个类里,一旦财务需求发生变化,你修改代码时就有可能不小心把运维或数据库相关的功能给改出 Bug。

为什么需要单一职责原则?

  1. 降低代码复杂度:类只专注于一件事,代码量少、逻辑清晰,非常容易阅读和维护。

  2. 减少修改带来的风险(降低耦合):修改某一个业务逻辑时,不会“牵一发而动全身”影响到其他无关功能。

  3. 提高复用性:功能越单一、越纯粹的类,越容易在其他地方被重复利用。

  4. 极易编写单元测试:测试目标非常明确,不需要写一堆复杂的伪数据(Mock)去覆盖不相关的逻辑。

Python 代码示例

结合你贴出的通知系统案例,我们来看一下违反和遵循 SRP 的对比:

❌ 违反 SRP 的做法(全能大杂烩类)
下面的 UserManager 类承担了太多职责:不仅管理用户数据,还负责密码加密、连接数据库,甚至还掺和了发送邮件!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class UserManager:
"""这是一个严重的‘大杂烩’类,违反了 SRP 原则。
引起它发生变化的原因太多了:
1. 用户数据字段变了(业务需求)
2. 密码加密算法变了(安全需求)
3. 邮件发送服务商变了(技术架构)
4. 数据库存储方式变了(底层技术)
"""
def register_user(self, username, password, email):
# 1. 职责一:校验与业务逻辑
if not username or not password:
raise ValueError("用户名或密码不能为空")

# 2. 职责二:密码加密
hashed_password = f"hash_{password}_secret"

# 3. 职责三:数据库存储逻辑
print(f"[数据库] 将用户 {username} 和密码 {hashed_password} 存入 MySQL...")

# 4. 职责四:发送邮件通知
print(f"[网络] 连接 SMTP 服务器,发送欢迎邮件至 {email}...")

遵循 SRP 的做法(分工明确,各司其职)

我们将上面的“大杂烩”按照职责独立拆分成不同的专业类,每一个类只负责一件事:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 1. 职责一:专门负责密码处理
class PasswordHasher:
def hash_password(self, password: str) -> str:
# 内部加密逻辑变化,完全不影响其他类
return f"hash_{password}_secret"


# 2. 职责二:专门负责用户数据的持久化存储
class UserRepository:
def save(self, username: str, hashed_password: str):
# 数据库从 MySQL 换成 MongoDB,只需要改这里
print(f"[数据库] 将用户 {username} 保存至数据库")


# 3. 职责三:专门负责发送邮件(结合了你提到的 MessageSender 思想)
class EmailNotifier:
def send_welcome_email(self, email: str):
# 邮件服务变化,只需要改这里
print(f"[邮件服务] 发送欢迎邮件至 {email}")


# 4. 职责四:核心业务组装者(只负责用户注册流程协调)
class UserService:
def __init__(self, hasher: PasswordHasher, repo: UserRepository, notifier: EmailNotifier):
self.hasher = hasher
self.repo = repo
self.notifier = notifier

def register_user(self, username: str, password: str, email: str):
# 业务逻辑极其清晰纯粹,只做流程控制
if not username or not password:
raise ValueError("用户名或密码不能为空")

hashed_pwd = self.hasher.hash_password(password)
self.repo.save(username, hashed_pwd)
self.notifier.send_welcome_email(email)

总结:SOLID 中 DIP 与 SRP 的微妙关系

  1. 单一职责原则(SRP) 关注的是类内部的“高内聚”:解决的是“一个类不要管太多闲事,防止代码交织乱成一团”的问题。
  2. 依赖倒转原则(DIP) 关注的是类与类之间的“低耦合”:解决的是“模块之间不要直接死板依赖,要通过接口/抽象打交道”的问题。