SRP(单一职责原则)— SOLID 的第一个原则,它规定:每个类或模块应该只有一个变更原因。这一原则由 Robert Martin 在 Clean Architecture(2017)一书中提出,并成为模块化设计的基础。根据这本书,应用 SRP 直接降低了组件间的耦合度,并在功能改进时消除了级联变更。
要点
SRP(单一职责原则) — 单一职责原则规定:每个类或模块应该只有一个变更原因。这并不意味着一个类只能执行一个操作。它是指一组相关操作,由一个职责对一个参与者统一负责。
Robert Martin 从参与者的角度重新阐述了 SRP:一个类应该只根据一个利益相关者或一个群体的请求进行变更。如果两个不同的参与者要求变更同一个类 — 说明职责划分不正确。
例如,同时计算薪资(财务部请求)和生成报告(管理层请求)的 Employee 类违反了 SRP。计算规则的变更可能会影响报告生成,反之亦然。
模块 应该有一个且只有一个变更原因。变更原因由参与者 — 发起需求的人员或系统来确定。如果来自不同参与者的需求导致一个模块的变更 — 该模块违反了 SRP。
参与者的概念使 SRP 成为架构分析的实用工具,而非抽象建议。在设计系统时,只需问一个问题:“谁会要求修改这段代码?” — 如果答案包含多个利益相关者,职责就应该分开。
单一职责 通过将因为一个原因而变更的方法分组来实现。类成为相关逻辑的“集合点”,而不是万能的“瑞士军刀”。这简化了代码的理解:开发者看到类就能立即理解其用途。
SRP 的工作机制基于 单一变更轴 规则。如果功能可能因独立原因而变更 — 应该将其移入单独的类。这些类之间的连接通过组合或委托来构建。
SRP 的违规表现为“上帝类”(God Objects),包含数十个处理不同数据的方法。这样的类难以测试 — 测试一个方法需要为所有其他方法配置环境。一个职责的变更可能会破坏另一个,使代码变得脆弱。
在实践中,SRP 帮助开发者回答“这段代码在哪里?”的问题。如果每个职责都分离到自己的类中,找到所需的文件只需几秒钟。在使用 MVVM 架构的 Android 项目中,这意味着 UserViewModel 只负责用户屏幕的状态,而 UserRepository 负责获取数据。寻找缓存逻辑的开发者去 UserCacheRepository,而不是 ViewModel。这样的代码组织 加速了新团队成员的入职,并减少了重构时的错误数量。
移动开发 对代码的模块化提出了特殊要求。Android Fragment 或 iOS ViewController 常常成为逻辑的“吸引点”:点击处理、API 调用、响应解析、UI 更新 — 全部在一个类中。SRP 要求分离这些职责。
在 Android 架构 中,SRP 已嵌入 Google 对 Jetpack 的建议中:ViewModel 负责屏幕状态,Repository 负责数据,UseCase 负责业务逻辑。每个组件都有一个变更原因。在 iOS 开发 中,MVVM 模式和 Coordinator 遵循相同的逻辑。
在移动项目中遵守 SRP 带来了可衡量的优势:类大小减少 40-60%,代码审查时间缩短,添加新功能时回归错误数量减少。隔离的模块 更容易被单元测试覆盖并在其他屏幕中复用。
单元测试 遵守 SRP 的类需要更少的模拟对象和配置。如果类只有一个职责,其依赖关系是有限的。测试检查一种行为,而不是多个不相关场景的组合。
根据 Google Testing Blog(2023)的报告,单一职责的类与聚合类相比,测试覆盖率高出 35%。开发者更愿意为小型、易理解的模块编写测试。
考虑一个典型的违反 SRP 的 Android 类 — 它同时加载数据、解析响应和更新 UI。重构后,每个职责被分离到独立的组件中。
// 违反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 中的类似示例,分离网络层和表示层:
// 违反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 违规是由 业务逻辑 和 UI 逻辑 在 Activity、Fragment 或 ViewController 中混合引起的。当 onClickListener 方法同时验证数据、调用 API 和更新按钮可见性时 — 这直接违反了单一职责原则。
违反 SRP 的后果包括:并行开发困难(一个文件中的冲突)、单元测试困难、变更成本高以及代码可读性降低。系统性违反 SRP 的项目需要 2-3 倍的时间 来添加新功能。
SRP 违规可以通过间接迹象来确定:类包含超过 200 行代码,从应用程序的不同层(UI + network + database)导入模块,拥有 5 个以上不同主题的公共方法。内聚性度量 — 统计指标:类内部方法的低内聚性表明 SRP 违规。
为了发现 SRP 违规,使用 静态分析工具 很有帮助:对于 Android — 使用 TooManyFunctions 规则的 Detekt,对于 iOS — 使用 file_length 规则的 SwiftLint。这些工具会标记超出大小和复杂性阈值的类。
违反 SRP 的类的重构通过 Extract Class 或 Extract Delegate 进行:一组相关方法被移入一个单独的类,原始类将调用委托给它们。逐步应用此类重构将“上帝类”转变为松耦合的模块集合,每个模块具有一个职责。这种方法 允许在不停止开发的情况下改进架构 — 重构以迭代方式执行,一次一个模块。
常见问题
不。SRP 关注的不是方法的数量,而是变更原因的数量。一个类可以有十个方法,如果它们都服务于对同一个参与者的同一职责。只有一个方法 — 是另一个极端,会导致代码过度碎片化。
这是同一个原则。单一职责原则 既可以翻译为“单一职责”,也可以翻译为“单一义务”。术语“职责”更准确地反映了本质:它是对参与者的责任,而不是技术功能。
Repository — 将 SRP 应用于数据层的直接结果。Repository 不是将数据访问逻辑分散到 ViewModel 或 UseCase 中,而是承担单一职责:通过源抽象提供数据。这是移动架构中 SRP 的经典实现。
可以,SRP 不禁止依赖关系。具有单一职责的类可以通过组合将部分工作委托给其他类。重要的是,这些被委托的任务是同一职责的一部分,而不是独立的变更原因。
问一个问题:“哪些参与者可能会要求更改这个类?”如果答案包含多个参与者 — SRP 被违反。另外:尝试用一句话描述类的用途,不使用“和”字连接。如果做不到 — 这个类做的事情太多了。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。