4、Repository模式:单一数据源原则,Repository的职责与设计

好,我们直接进入正题。Repository模式,说白了就是给数据访问加个「中间人」。你想想看,ViewModel想要数据,它不用管数据是从网络来的,还是从本地数据库来的,它只需要跟Repository说一声「给我数据」,Repository就会搞定一切。

我在项目中遇到过很多次,团队里有人直接在ViewModel里写网络请求,结果后来要加缓存,改得那叫一个痛苦。嗯,这就是典型的「没有中间商赚差价」的后果。

4.1 单一数据源原则

单一数据源,英文叫Single Source of Truth,简称SSOT。这个原则的核心思想是:数据只有一个权威来源

举个例子,你的App里显示用户信息。如果网络请求返回了用户数据,本地数据库也存了一份,那到底以哪个为准?

核心结论:Repository就是那个「唯一权威」。所有数据请求都经过它,所有数据更新也由它统一管理。

我曾经接手过一个项目,网络层和数据库层各自为政。结果用户改了昵称,列表页显示新昵称,详情页还是旧昵称。排查了半天,发现是网络请求和本地缓存没有同步。从那以后,我强制要求所有数据必须走Repository。

4.2 Repository的职责

Repository到底该干什么?我总结下来就三件事:

  • 数据调度:决定从网络拿数据,还是从本地拿数据
  • 数据转换:把网络返回的DTO(Data Transfer Object)转成Domain层需要的Model
  • 数据同步:确保网络数据和本地数据保持一致

说白了,Repository就是数据层的「交通警察」。它不生产数据,它只是数据的搬运工。

4.3 典型设计

来看一个我常用的设计模板。先定义接口:

interface UserRepository {
    suspend fun getUser(id: String): Result<User>
    suspend fun updateUser(user: User): Result<Unit>
    suspend fun deleteUser(id: String): Result<Unit>
}

然后是实现类:

class UserRepositoryImpl(
    private val remoteDataSource: UserRemoteDataSource,
    private val localDataSource: UserLocalDataSource
) : UserRepository {

    override suspend fun getUser(id: String): Result<User> {
        return try {
            // 先尝试从网络获取
            val remoteUser = remoteDataSource.getUser(id)
            // 同步到本地
            localDataSource.saveUser(remoteUser)
            Result.success(remoteUser.toDomain())
        } catch (e: Exception) {
            // 网络失败,从本地拿缓存
            val localUser = localDataSource.getUser(id)
            if (localUser != null) {
                Result.success(localUser.toDomain())
            } else {
                Result.failure(e)
            }
        }
    }
}

我的习惯:Repository只返回Result类型,不抛异常。这样上层处理起来更干净,不用到处try-catch。

4.4 数据流设计

来看一张我画的流程图,帮你理解数据是怎么流转的:

Repository 数据流架构图 ViewModel Repository Remote DataSource Local DataSource 请求数据 优先尝试 缓存/回退 实线:请求路径 | 虚线:返回路径

你看,ViewModel只跟Repository打交道。Repository先尝试网络,失败了就回退到本地缓存。这就是典型的「离线优先」策略。

4.5 避坑指南

我曾经踩过的坑:

  • 不要在Repository里做UI相关的逻辑,比如格式化时间、拼接字符串。那是ViewModel的事。
  • 不要直接暴露DataSource给ViewModel。我见过有人图省事,在Repository里加了个getRemoteDataSource()方法,结果ViewModel直接调了网络接口,缓存策略形同虚设。
  • 注意线程安全。如果多个ViewModel同时请求数据,Repository要做好并发控制。我一般用Kotlin的Mutex或者Channel来处理。

4.6 什么时候用Repository?

不是所有项目都需要Repository。我个人的判断标准很简单:

场景 是否推荐Repository 原因
只有一个数据源(比如纯网络) 可选 简单项目可以直接用ViewModel+网络层
多个数据源(网络+缓存+数据库) 强烈推荐 统一管理数据来源,避免混乱
需要离线功能 必须 Repository是离线策略的最佳载体
团队协作项目 推荐 职责清晰,新人上手快

我的建议:哪怕项目初期只有一个数据源,我也建议先搭好Repository的架子。因为后期加缓存、加数据源是大概率事件。到时候再重构,成本比现在搭架子高得多。

4.7 小结

Repository模式的核心就一句话:让数据访问变得简单、统一、可测试

你想想看,ViewModel不需要知道数据从哪来,测试的时候只需要mock Repository接口,多清爽。这就是Clean Architecture里依赖倒置原则的体现——高层模块不依赖低层模块,大家都依赖抽象。

嗯,这一章的内容就到这里。记住,Repository不是银弹,但它确实是Android开发中管理数据的最佳实践之一。


公众号:蓝海资料掘金营,微信deep3321