单一职责原则(Single Responsibility Principle, SRP)是面向对象设计五大原则(SOLID)中的第一个,其核心思想是:一个类(或模块、函数)应该且仅应有一个引起它变化的原因,即只承担一种职责。
- ✅ 正确实践:如
UserRepository仅负责数据库的用户数据存取,UserService仅负责业务逻辑编排; - ❌ 违反表现:一个
UserManager类既处理用户注册验证、又写日志、又发邮件、又更新缓存——任一需求变更(如更换邮件服务)都可能迫使修改该类,导致高耦合、低内聚、测试困难、易出错; - 🛠 应对策略:通过职责识别→提取共性→拆分为更小单元(如
EmailNotifier、CacheUpdater),再用组合(而非继承)或设计模式(如代理模式封装调用、装饰器模式动态增强行为)解耦协作。
# 示例:违反SRP的类(重构前)
class UserManager:
def register(self, user):
# 验证 + 保存 + 发邮件 + 记日志 → 四种职责混杂
if not user.is_valid(): return False
db.save(user)
send_email(user.email, "Welcome!")
log.info(f"User {user.id} registered")
return True
# 重构后:各司其职
class UserRepository:
def save(self, user): ...
class EmailNotifier:
def notify(self, user): ...
class UserRegistrationService:
def __init__(self, repo: UserRepository, notifier: EmailNotifier, logger: Logger):
self.repo = repo
self.notifier = notifier
self.logger = logger
def register(self, user):
if not user.is_valid(): return False
self.repo.save(user)
self.notifier.notify(user)
self.logger.info(...)
return True
准确识别一个类是否违反单一职责原则(SRP),关键在于审视其变更原因的数量:若该类因多种不同业务目标、技术需求或利益相关方(如产品经理、运维、安全团队)的要求而需要修改,即存在多个“变化原因”,则已违反SRP。
以下是常见的 “职责信号”(Smells of SRP Violation),可作为代码审查和重构的实用检查清单:
✅ 1. 方法功能跨度大
- 同一方法中混合:数据校验 + 持久化 + 网络调用 + 日志记录 + 通知发送;
- 类中同时出现
saveToDB()、sendEmail()、generateReport()、validateInput()等语义完全无关的方法。
✅ 2. 引入过多依赖(高耦合)
- 类的构造函数/注入列表包含跨领域对象:如同时依赖
DatabaseConnection、SMTPClient、RedisCache、MetricsReporter、AuditLogger;
→ 暗示它在协调多个系统关注点,而非专注自身核心逻辑。
✅ 3. 注释/命名暴露多意图
- 类名含“Manager”“Handler”“Processor”“Utils”等模糊词(如
OrderManager、FileProcessor)且无明确边界; - 方法注释频繁出现“同时…”“另外…”“还要…”(例:“验证用户,同时更新积分,还要发站内信并记录审计日志”)。
✅ 4. 测试用例类型混杂
- 单元测试文件中需 mock 数据库、邮件服务、第三方API、缓存等多个外部系统;
- 一个测试方法既验证业务规则,又断言日志内容、邮件模板、HTTP状态码——说明测试在验证多个契约。
✅ 5. 修改引发意外副作用
- 仅因“增加短信通知”就需修改该类,结果导致原有邮件发送逻辑出错或缓存失效;
- 发布后出现非预期故障(如改日志级别导致事务回滚失败),表明职责纠缠。
🔍 辅助判断技巧:
- “如果……会怎样?”提问法:
▪ 如果产品要求“注册成功后不再发邮件”,是否要改这个类?
▪ 如果运维要求“日志格式统一为JSON”,是否要改这个类?
▪ 如果安全团队要求“密码字段脱敏打印”,是否要改这个类?
→ 若任一答案为“是”,即存在 ≥2 个变化原因,SRP 已被破坏。
# 反例信号集中体现
class PaymentService { // ❌ 名称宽泛,无领域聚焦
private Database db;
private EmailSender email;
private SmsGateway sms;
private Cache cache;
private AuditLogger audit;
public void process(Payment p) {
if (!p.isValid()) throw ...; // 验证
db.insert(p); // 存储
cache.evict("user:" + p.userId); // 缓存
email.send("paid", p.user); // 通知
sms.send(p.user.phone, "Paid!"); // 另一通知
audit.log("Payment processed", p.id); // 审计
}
}
// ✅ 重构方向:拆为 PaymentValidator、PaymentRepository、NotificationService、AuditService 等


788

被折叠的 条评论
为什么被折叠?



