Repository模式 — 一种在业务逻辑和数据源之间添加抽象层的模式。Repository提供统一的接口来获取和保存数据,而不是直接调用API、数据库或缓存。这简化了测试和源之间的切换。更多详情请参见Android Data Layer文档。
要点
Repository模式 — 一种将业务逻辑与数据源直接访问隔离开的结构性模式。Activity、UIViewController或ViewModel不再直接调用Retrofit、URLSession、Room或CoreData,而是向Repository请求数据。Repository决定从哪里获取数据:网络、数据库还是缓存,并以统一格式返回结果。这是单一职责原则的实现——UI不知道数据是如何以及从哪里获取的。
Repository的组件包括接口(protocol)、实现以及一个或多个DataSource。DataSource是与单个源一起工作的类:RemoteDataSource通过HTTP客户端调用API,LocalDataSource读写数据库。Repository通过构造函数接收DataSource(依赖注入)并选择要访问哪个源。例如,在请求用户列表时,Repository首先检查缓存,然后是数据库,最后是网络。
Repository模式的优点:数据源的更改(API变更、数据库迁移)不会影响UI层;通过替换Repository或DataSource进行单元测试;缓存对UI透明;无需更改屏幕逻辑即可在在线和离线模式之间切换。Android社区推荐Repository作为Clean Architecture中的必要层。
iOS实现 Repository基于Swift协议构建。Repository协议声明获取和保存数据的方法。实际实现通过初始化器注入——这允许在测试和SwiftUI预览中替换实现。DataSource也通过协议声明:Protocol RemoteDataSource、Protocol LocalDataSource。ViewModel或Interactor不知道具体实现——只知道Repository协议。
protocol UserRepository {
func getUsers() async throws -> [User]
}
protocol UserRemoteDataSource {
func fetchUsers() async throws -> [User]
}
protocol UserLocalDataSource {
func getCachedUsers() throws -> [User]
func saveUsers(_: [User]) throws
}
final class UserRepositoryImpl: UserRepository {
private let remote: UserRemoteDataSource
private let local: UserLocalDataSource
init(remote: UserRemoteDataSource, local: UserLocalDataSource) {
self.remote = remote
self.local = local
}
func getUsers() async throws -> [User] {
if let cached = try? local.getCachedUsers() {
return cached
}
let users = try await remote.fetchUsers()
try local.saveUsers(users)
return users
}
}
依赖注入在iOS中通常通过工厂或DI容器(Swinject、Factory)进行配置。在测试中,UserRepository协议被返回预定义数据的mock实现替换。Async-await使代码保持同步和可读,无需闭包和委托。对于Combine响应式编程,Repository方法返回AnyPublisher而不是async throws。
Android实现 Repository广泛使用Kotlin Coroutines和Flow进行异步工作。Google在官方Android架构指南(Android Architecture Components)中推荐Repository。Repository通过构造函数接收RemoteDataSource(Retrofit)和LocalDataSource(Room),ViewModel订阅来自Repository的Flow。Repository管理数据策略:先缓存、后网络,或始终网络,写入缓存。
interface UserRepository {
fun getUsers(): Flow<Result<List<User>>>
}
interface UserRemoteDataSource {
suspend fun fetchUsers(): List<User>
}
interface UserLocalDataSource {
fun getCachedUsers(): Flow<List<User>>
suspend fun saveUsers(users: List<User>)
}
class UserRepositoryImpl(
private val remote: UserRemoteDataSource,
private val local: UserLocalDataSource
) : UserRepository {
override fun getUsers(): Flow<Result<List<User>>> = flow {
emit(Result.Loading)
local.getCachedUsers().collect { cached ->
if (cached.isNotEmpty()) {
emit(Result.Success(cached))
}
}
try {
val users = remote.fetchUsers()
local.saveUsers(users)
emit(Result.Success(users))
} catch (e: Exception) {
emit(Result.Error(e))
}
}
}
Result包装器在上面的示例中是Android的标准做法:sealed class Result通知ViewModel加载状态(Loading、Success、Error)。ViewModel通过collect订阅并更新StateFlow或LiveData。带Flow的Repository自动通知UI数据库中的更改——这是与一次性查询的关键区别,一次性查询中UI不会在不手动刷新的情况下知道更改。
DataSource — 负责与特定数据源一起工作的类。RemoteDataSource使用HTTP客户端(URLSession、Retrofit、Ktor)从API获取数据。LocalDataSource与本地存储(CoreData、Realm、Room、UserDefaults、DataStore)一起工作。每个DataSource具有狭窄的职责:RemoteDataSource只知道API请求格式,LocalDataSource知道数据库模式。Repository组合它们,实现缓存策略。
| DataSource | iOS平台 | Android平台 | 来源 |
|---|---|---|---|
| 远程 | URLSession + Codable | Retrofit + Moshi/Gson | REST / GraphQL API |
| 本地(数据库) | CoreData、SwiftData | Room、SQLDelight | 设备上的SQLite |
| 本地(缓存) | NSCache、UserDefaults | DataStore、EncryptedSP | 内存/磁盘 |
| 偏好设置 | UserDefaults、Keychain | SharedPreferences、EncryptedSP | 设置、令牌 |
Repository中的缓存策略:Cache-First(先缓存,然后后台加载)、Network-Only(仅网络,用于支付屏幕)、Network-First-With-Cache-Backup(先网络,出错时使用缓存)。策略选择取决于场景:国家列表可以长期缓存,汇率——15分钟,钱包余额——仅来自网络。Repository实现策略并在不修改ViewModel或UI的情况下更改策略。
Repository和Service — 具有重叠功能的不同模式。Repository负责数据访问和缓存,返回数据模型。Service(或Interactor、Use Case)包含业务逻辑:验证、数据转换、协调多个Repository的调用。Service可以组合UserRepository、OrderRepository和NotificationRepository来处理订单。Repository不包含业务逻辑——仅CRUD和缓存。
何时选择Repository — 多源数据导航(API + 数据库 + 缓存)、offline-first架构、需要缓存和透明源切换。Repository在Clean Architecture中是必需的,Google推荐用于Android应用程序。在iOS的VIPER架构中,Repository的角色由Interactor层扮演,该层与Manager或Service交互以访问数据。
何时Service足够 — 具有单一数据源的简单应用程序、无写入的只读屏幕、无离线模式的项目。在这种情况下,DataSource直接由ViewModel或Presenter使用,Repository成为多余层。然而,在早期阶段添加Repository不需要大量成本,并且简化了未来添加缓存和测试的过程。
常见问题
DataSource — 与单一源(API、数据库、缓存)一起工作的类。Repository — 管理多个DataSource并提供统一接口的类。Repository决定从哪个DataSource获取数据并协调缓存。DataSource不知道其他源的存在,Repository不知道每个源的实现细节。
是的,Repository在SwiftUI中对于将数据与视图分离非常有用。ViewModel订阅Repository的Publisher,Repository管理缓存和同步。在简单应用中可以直接在ViewModel中使用URLSession,但为了可测试性和可扩展性,Repository更可取。Apple不强制使用此模式,但它与SwiftData和Network.framework兼容。
DataSource通过依赖注入替换为mock对象。测试创建一个mock RemoteDataSource(返回预定义JSON)和一个mock LocalDataSource(检查数据是否已保存)。Repository被隔离测试:检查缓存策略、错误处理和正确的调用顺序。对于集成测试,使用TestDispatcher(Kotlin)或MainActor.run(Swift)。
可以,但不推荐。没有协议就无法在测试和预览中替换实现。在Kotlin中,Repository接口允许通过DI(Dagger、Hilt、Koin)替换实现。在Swift中,Repository协议对于测试async-await和Combine代码是必需的。例外情况——具有单一数据源且Repository不包含缓存逻辑的简单项目。
Offline-first — 一种应用无需互联网即可使用本地数据运行的策略。Repository发挥关键作用:首先从本地DataSource返回数据,然后在后台与服务器同步。用户立即看到数据,Repository在从网络加载后更新数据。带Flow的Room在本地数据库数据更改时提供UI的响应式更新。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。