SRP:什么是单一职责原则及其在开发中的应用

作者: IT Sectr 发布日期: 2026-05-11 阅读时间: 9 分钟

SRP(单一职责原则)— SOLID 的第一个原则,它规定:每个类或模块应该只有一个变更原因。这一原则由 Robert Martin 在 Clean Architecture(2017)一书中提出,并成为模块化设计的基础。根据这本书,应用 SRP 直接降低了组件间的耦合度,并在功能改进时消除了级联变更。

要点

  • SRP — SOLID 的第一个原则,要求每个类只有一个职责
  • 变更原因 — 将职责划分到模块的唯一标准
  • 违反 SRP 会导致代码耦合度高,难以测试和扩展
  • 应用该原则 简化了重构并降低了回归错误的风险
  • 移动开发中的 SRP 有助于分离 UI 逻辑、业务规则和数据处理

什么是 SRP(单一职责原则)?

SRP(单一职责原则) — 单一职责原则规定:每个类或模块应该只有一个变更原因。这并不意味着一个类只能执行一个操作。它是指一组相关操作,由一个职责对一个参与者统一负责。

Robert Martin 从参与者的角度重新阐述了 SRP:一个类应该只根据一个利益相关者或一个群体的请求进行变更。如果两个不同的参与者要求变更同一个类 — 说明职责划分不正确。

例如,同时计算薪资(财务部请求)和生成报告(管理层请求)的 Employee 类违反了 SRP。计算规则的变更可能会影响报告生成,反之亦然。

SRP 的正式定义

模块 应该有一个且只有一个变更原因。变更原因由参与者 — 发起需求的人员或系统来确定。如果来自不同参与者的需求导致一个模块的变更 — 该模块违反了 SRP。

参与者的概念使 SRP 成为架构分析的实用工具,而非抽象建议。在设计系统时,只需问一个问题:“谁会要求修改这段代码?” — 如果答案包含多个利益相关者,职责就应该分开。

单一职责原则如何工作

单一职责 通过将因为一个原因而变更的方法分组来实现。类成为相关逻辑的“集合点”,而不是万能的“瑞士军刀”。这简化了代码的理解:开发者看到类就能立即理解其用途。

SRP 的工作机制基于 单一变更轴 规则。如果功能可能因独立原因而变更 — 应该将其移入单独的类。这些类之间的连接通过组合或委托来构建。

SRP 的违规表现为“上帝类”(God Objects),包含数十个处理不同数据的方法。这样的类难以测试 — 测试一个方法需要为所有其他方法配置环境。一个职责的变更可能会破坏另一个,使代码变得脆弱。

在实践中,SRP 帮助开发者回答“这段代码在哪里?”的问题。如果每个职责都分离到自己的类中,找到所需的文件只需几秒钟。在使用 MVVM 架构的 Android 项目中,这意味着 UserViewModel 只负责用户屏幕的状态,而 UserRepository 负责获取数据。寻找缓存逻辑的开发者去 UserCacheRepository,而不是 ViewModel。这样的代码组织 加速了新团队成员的入职,并减少了重构时的错误数量。

为什么 SRP 在移动开发中很重要

移动开发 对代码的模块化提出了特殊要求。Android Fragment 或 iOS ViewController 常常成为逻辑的“吸引点”:点击处理、API 调用、响应解析、UI 更新 — 全部在一个类中。SRP 要求分离这些职责。

Android 架构 中,SRP 已嵌入 Google 对 Jetpack 的建议中:ViewModel 负责屏幕状态,Repository 负责数据,UseCase 负责业务逻辑。每个组件都有一个变更原因。在 iOS 开发 中,MVVM 模式和 Coordinator 遵循相同的逻辑。

在移动项目中遵守 SRP 带来了可衡量的优势:类大小减少 40-60%,代码审查时间缩短,添加新功能时回归错误数量减少。隔离的模块 更容易被单元测试覆盖并在其他屏幕中复用。

SRP 对测试的影响

单元测试 遵守 SRP 的类需要更少的模拟对象和配置。如果类只有一个职责,其依赖关系是有限的。测试检查一种行为,而不是多个不相关场景的组合。

根据 Google Testing Blog(2023)的报告,单一职责的类与聚合类相比,测试覆盖率高出 35%。开发者更愿意为小型、易理解的模块编写测试。

Android 和 iOS 中的 SRP 示例

考虑一个典型的违反 SRP 的 Android 类 — 它同时加载数据、解析响应和更新 UI。重构后,每个职责被分离到独立的组件中。

kotlin
// 违反SRP:一个类做所有事情
class BadUserProfileActivity {
    fun loadUser(userId: Int) {
        // HTTP请求
        // JSON解析
        // UI更新
        // 保存到数据库
    }
}

// 应用SRP之后
class UserRepository {
    fun getUser(userId: Int): User
}

class UserViewModel {
    private val repo: UserRepository
    fun loadUser(userId: Int) { }
}

class UserProfileFragment {
    fun render(user: User) { }
}

iOS Swift 中的类似示例,分离网络层和表示层:

swift
// 违反SRP:ViewController管理数据和UI
class BadProfileViewController: UIViewController {
    func viewDidLoad() {
        // URLSession请求
        // JSON解码
        // 标签更新
    }
}

// 应用SRP之后
protocol UserServiceProtocol {
    func fetchUser(id: Int) async throws -> User
}

class ProfileViewModel {
    private let service: UserServiceProtocol
    func loadProfile(id: Int) { }
}

class ProfileViewController: UIViewController {
    func display(user: User) { }
}

SRP 重构 不会使架构复杂化 — 它重新分配了职责。代码量甚至可能通过消除重复而减少。每个新类都有明确的目标,可以独立开发。

组合作为继承的替代方案

组合 在继承造成不必要耦合的地方有助于遵循 SRP。子类不是继承拥有数十个方法的超类,而是通过构造函数接收一组专门化的对象。每个对象负责自己的功能。

在 Android 开发中,Decorator 模式允许在不修改原始类的情况下添加职责。在 iOS 中,网络层的 Middleware 链将日志记录、缓存和身份验证分离到独立的模块中。

常见的 SRP 违规及其后果

最常见的违规 — “上帝类”:一个同时管理数据库、发送通知、生成报告和处理用户输入的类。这样的类成为项目的瓶颈:每次变更都需要完整的回归测试。

在移动开发中,SRP 违规是由 业务逻辑UI 逻辑 在 Activity、Fragment 或 ViewController 中混合引起的。当 onClickListener 方法同时验证数据、调用 API 和更新按钮可见性时 — 这直接违反了单一职责原则。

违反 SRP 的后果包括:并行开发困难(一个文件中的冲突)、单元测试困难、变更成本高以及代码可读性降低。系统性违反 SRP 的项目需要 2-3 倍的时间 来添加新功能。

代码中 SRP 违规的指标

SRP 违规可以通过间接迹象来确定:类包含超过 200 行代码,从应用程序的不同层(UI + network + database)导入模块,拥有 5 个以上不同主题的公共方法。内聚性度量 — 统计指标:类内部方法的低内聚性表明 SRP 违规。

为了发现 SRP 违规,使用 静态分析工具 很有帮助:对于 Android — 使用 TooManyFunctions 规则的 Detekt,对于 iOS — 使用 file_length 规则的 SwiftLint。这些工具会标记超出大小和复杂性阈值的类。

违反 SRP 的类的重构通过 Extract Class 或 Extract Delegate 进行:一组相关方法被移入一个单独的类,原始类将调用委托给它们。逐步应用此类重构将“上帝类”转变为松耦合的模块集合,每个模块具有一个职责。这种方法 允许在不停止开发的情况下改进架构 — 重构以迭代方式执行,一次一个模块。

常见问题

SRP 是否意味着一个类应该只包含一个方法?

不。SRP 关注的不是方法的数量,而是变更原因的数量。一个类可以有十个方法,如果它们都服务于对同一个参与者的同一职责。只有一个方法 — 是另一个极端,会导致代码过度碎片化。

SRP 与单一义务原则有何不同?

这是同一个原则。单一职责原则 既可以翻译为“单一职责”,也可以翻译为“单一义务”。术语“职责”更准确地反映了本质:它是对参与者的责任,而不是技术功能。

SRP 与 Repository 模式有何关系?

Repository — 将 SRP 应用于数据层的直接结果。Repository 不是将数据访问逻辑分散到 ViewModel 或 UseCase 中,而是承担单一职责:通过源抽象提供数据。这是移动架构中 SRP 的经典实现。

具有 SRP 的类能否依赖其他类?

可以,SRP 不禁止依赖关系。具有单一职责的类可以通过组合将部分工作委托给其他类。重要的是,这些被委托的任务是同一职责的一部分,而不是独立的变更原因。

如何检查一个类是否遵循 SRP?

问一个问题:“哪些参与者可能会要求更改这个类?”如果答案包含多个参与者 — SRP 被违反。另外:尝试用一句话描述类的用途,不使用“和”字连接。如果做不到 — 这个类做的事情太多了。

总结

  • SRP(单一职责原则)— SOLID 的第一个原则,要求类有一个变更原因
  • 变更原因 由参与者 — 向模块提出需求的人员或系统 — 确定
  • 违反 SRP 导致上帝类、低可测试性和高变更成本
  • 在移动开发中 SRP 将 UI 逻辑、业务逻辑和数据处理分离到独立的组件中
  • 组合 通过委托给专门化的对象,比继承更有效地帮助遵循 SRP
  • 静态分析工具(Detekt、SwiftLint)自动检测潜在的 SRP 违规
  • 单元测试 具有 SRP 的类需要更少的模拟对象,并显示更高的代码覆盖率

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读