关注点分离(Separation of Concerns)是一种原则,按照该原则,应用程序的每个模块或层负责一个责任领域。根据 Wikipedia,该术语由 Edsger Dijkstra 于 1974 年提出,此后成为软件架构的基础。分离责任使开发人员能够更改一个代码层而不影响其他层,这在具有长支持周期的移动项目中至关重要。
要点
关注点分离(Separation of Concerns)是将软件系统分解为独立部分的原则,每个部分解决一个任务。术语 concern(责任领域)表示功能的任何可分离部分:屏幕显示、点击处理、数据验证或网络通信。该原则规定代码的分组方式,使得一个领域的更改不需要在其他领域进行更改。
在移动开发中,SoC 在多个层面上体现:从应用程序划分为屏幕到单个类的代码组织。同时从网络加载数据、解析 JSON 和绘制 UI 的 Activity 或 ViewController 违反了关注点分离——这样的代码难以维护、测试和扩展。替代方案——将每种责任类型转移到单独的组件中。
该原则与抽象的概念密切相关:每个层提供严格定义的接口并隐藏实现细节。得益于此,开发人员可以在不重写 UI 逻辑的情况下更换网络库或数据库。这在需求和会随时间变化的长期项目中尤为有价值。
Edsger Dijkstra 在 1974 年的文章 «On the Role of Scientific Thought» 中首次阐述了关注点分离的思想。他认为,软件系统的复杂性可以通过将其分解为隔离分析的部分来控制。这种方法与当时代码混合了计算、输入输出和用户界面的单体程序形成了对比。
在 20 世纪 80 年代,这一思想由结构化编程的支持者以及后来的面向对象方法进一步发展。Smalltalk 和 C++ 等语言提供了封装和模块化机制,使 SoC 成为实用的工具。现代架构模式——MVC、MVP、MVVM 和 Clean Architecture——是关注点分离原则的直接体现。
在移动开发世界中,Apple 将 MVC 推广为 iOS 的标准,其中 Model-View-Controller 分离了数据、显示和控制逻辑。Google 为 Android 提供了基于 ViewModel 和 Repository 的架构建议——每个组件解决其特定的任务。没有 SoC,移动应用程序会变成 Massive View Controller——拥有数千行代码的类,其中任何更改都有可能破坏全部功能。
四个主要层构成了实现关注点分离的典型移动应用程序架构。每个层仅负责自己的领域,并通过接口与相邻层交互。
View 专门负责显示数据和处理用户事件。在 iOS 中是 UIViewController 和 UIView,在 Android 中是 Fragment 或 Activity。ViewModel 包含屏幕状态和将数据转换为可供显示格式的逻辑。分离保证了用 SwiftUI 替换 UIKit 或用 Jetpack Compose 重写屏幕不会影响业务逻辑。
ViewModel 的测试不需要启动模拟器——检查数据转换和对用户操作反应的单元测试就足够了。这是关注点分离的直接结果:UI 不与业务规则混合,每个组件隔离测试。
Use Case(或 Interactor)包含应用程序的业务规则——计算、验证、对数据调用的编排。该层不知道 UI 和平台框架的存在。Use Case 从 Repository 获取数据,对其应用逻辑,并将准备好的结果返回给 ViewModel。分离允许在不同的屏幕上重用同一个 Use Case。
例如,LoginUseCase 检查电子邮件的有效性,调用 AuthRepository 进行身份验证并返回结果。它不依赖于登录屏幕的外观——SwiftUI、UIKit 或 Compose。如果业务规则发生变化,只需修改一个 Use Case,而无需触及 UI 和数据库。
Repository 抽象了数据源:远程 API、本地数据库或内存缓存。ViewModel 和 Use Case 不知道数据的确切来源——Repository 决定是从网络还是缓存加载。这种分离允许在不影响业务逻辑和 UI 的情况下更改存储实现。
DataSource 是更低层次的分离:NetworkDataSource 仅负责 HTTP 请求,LocalDataSource 负责与 Room 或 CoreData 一起工作。Repository 将对不同 DataSource 的调用组合成统一的连贯接口。每个 DataSource 使用 mock 或假服务器进行独立测试。
DataSource 层的正确实现保证了数据库模式更改或 REST API 替换为 GraphQL 只会影响一个 DataSource,而不会影响 Repository 及其消费者。这是基础设施层面上关注点分离的直接结果:每个 technical concern 被隔离并可在没有级联更改的情况下替换。
MVVM(Model-View-ViewModel)——移动开发中最流行的模式,直接实现了关注点分离。Model 包含数据和业务逻辑,View 负责显示,ViewModel 通过响应式机制将它们连接起来。在 Flutter 中,BLoC 扮演了类似角色,将事件、状态和业务逻辑分开。
Clean Architecture(整洁架构)由 Robert Martin(Uncle Bob)提出,将 SoC 最大化:系统分为独立的环——实体、用例、适配器和框架。内环(实体)不依赖外环(框架)。这允许更改数据库、UI 框架甚至平台,而无需重写应用程序的核心逻辑。
在实践中,移动项目很少实现完整的 Clean Architecture——对于大多数应用程序来说,三层架构就足够了:UI、Domain 和 Data。Domain 层包含 Use Cases 和业务模型,并且完全与 Android SDK 或 iOS SDK 隔离。这种分离以 20% 的努力提供了 80% 的好处。
// Data layer —— 仅负责获取数据
class UserRepository(private val api: UserApi) {
suspend fun getUser(id: String): User = api.fetchUser(id)
}
// Domain layer —— 业务逻辑,不关心 API 或数据库
class GetUserNameUseCase(
private val repo: UserRepository
) {
suspend fun invoke(id: String): String {
val user = repo.getUser(id)
return "${user.firstName} ${user.lastName}"
}
}
// UI layer —— 仅负责显示
class UserViewModel(
private val getUserName: GetUserNameUseCase
) {
fun onUserLoaded(id: String) {
viewModelScope.launch {
_name.value = getUserName.invoke(id)
}
}
}
上面的代码展示了清晰的分离:UserRepository 仅与 API 交互,GetUserNameUseCase 包含名称格式化的业务逻辑,UserViewModel 管理 UI 状态。每个类都有一个改变的原因,这就是关注点分离的本质。
SoC 的主要优势——可维护性。分成独立层的代码更容易分析:开发人员只查看发生错误的层,而不会被其他层干扰。在长期项目中,与单体代码相比,这可将错误查找和修复时间减少 30–50%。
第二个重要优势——可测试性。当业务逻辑与 UI 和框架隔离时,可以用单元测试覆盖,而无需启动模拟器。具有高单元测试覆盖率的 Android 和 iOS 项目在添加新功能时具有显著更少的回归。
主要局限性——复杂性增加。过度细分为微层和抽象导致开发人员为添加一个简单按钮而编辑五个文件。关注点分离原则需要合理的平衡:只分离那些真正独立变化的领域。对于小型项目,基本分离为 UI、逻辑和数据,无需额外的抽象,就足够了。
常见问题
SoC 是根据责任领域进行分离的原则,而模块化是将代码组织到物理模块中的方式。SoC 可以通过层或类在一个模块内实现,而模块化需要分割为独立的构建。
SoC 是 SOLID 原则之上的上层建筑。单一职责原则(S)是单个类级别的 SoC。依赖倒置原则(D)通过接口和依赖注入帮助在层之间实现 SoC。
是的,但需要适度。对于简单的应用程序,分离 UI 和业务逻辑就足够了。过多的层数会使代码变得复杂而没有实际好处。随着项目的增长,层数逐渐增加。
对性能没有直接影响——SoC 涉及代码架构,而非执行。然而,分层可能因层之间的额外调用而增加间接负载。在实践中,与可维护性的好处相比,这种影响可以忽略不计。
依赖注入(Hilt、Koin、Swinject)显式管理层之间的边界。Detekt(Android)和 SwiftLint(iOS)中的架构 linter 规则禁止从不允许的层导入。Git hooks 可以检查业务层是否不导入 UI 库。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。