You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MVVM重构:多网络请求链式调用应置于UseCase还是Repository?

你的思路整体是可行的,但需要调整职责边界来让架构更清晰、更易维护——这也是UseCase模式在复杂业务场景下的核心价值所在。下面我帮你拆解具体的实现要点和需要注意的细节:

核心可行性分析

你提出的「ViewModel调用UseCase → UseCase编排多请求链式逻辑 → 生成LiveData回传VM → 同时更新Repository的Subject分发全局数据」这个流程是合理的,但关键要明确各层的职责边界:

  • UseCase:负责业务逻辑编排(包括多请求组合、数据转换、业务规则判断)
  • Repository:专注数据获取与分发(单个请求封装、缓存管理、全局数据更新通知)
  • ViewModel:只做UI状态管理,不处理业务或数据逻辑

这样调整后,Repository会比原来更精简,UseCase则承担起复杂网络逻辑的维护工作,避免了单例Repository被多个ViewModel依赖导致的代码臃肿问题。

具体实现步骤示例

1. 重构Repository:聚焦单一数据请求

把原来的链式逻辑从Repository中移除,只保留单个请求的封装,同时维护Subject用于全局数据更新通知:

class UserRepository @Inject constructor(private val api: ApiService) {
    // 用于分发用户数据更新的Subject(用hide()避免外部直接发送事件)
    private val userUpdatedSubject = PublishSubject<User>()
    val userUpdatedObservable: Observable<User> = userUpdatedSubject.hide()

    // 单个请求:获取用户信息
    fun getUser(userId: Int): Observable<User> {
        return api.getUser(userId)
            .doOnNext { userUpdatedSubject.onNext(it) } // 请求完成后通知全局
            .subscribeOn(Schedulers.io())
    }

    // 单个请求:获取用户帖子
    fun getUserPosts(userId: Int): Observable<List<Post>> {
        return api.getUserPosts(userId)
            .subscribeOn(Schedulers.io())
    }
}

2. 实现UseCase:负责多请求编排和业务逻辑

创建专门的UseCase来处理Observable.zip组合请求,以及业务逻辑处理(比如关联用户和帖子):

// 定义数据封装类,用于组合多请求结果
data class UserWithPosts(val user: User, val posts: List<Post>)

class GetUserWithPostsUseCase @Inject constructor(private val userRepo: UserRepository) {
    fun execute(userId: Int): LiveData<Result<UserWithPosts>> {
        val combinedObservable = Observable.zip(
            userRepo.getUser(userId),
            userRepo.getUserPosts(userId),
            { user, posts -> UserWithPosts(user, posts) }
        )
        .map { Result.success(it) }
        .onErrorReturn { Result.failure(it) }

        // 转换为LiveData,适配ViewModel生命周期
        return LiveDataReactiveStreams.fromPublisher(combinedObservable)
    }
}

3. ViewModel中调用UseCase

ViewModel只需依赖UseCase,不用直接操作Repository,逻辑更简洁:

class UserViewModel @Inject constructor(
    private val useCase: GetUserWithPostsUseCase,
    private val userRepo: UserRepository
) : ViewModel() {
    private val _userWithPosts = MutableLiveData<Result<UserWithPosts>>()
    val userWithPosts: LiveData<Result<UserWithPosts>> = _userWithPosts

    fun loadUserWithPosts(userId: Int) {
        useCase.execute(userId).observeForever { result ->
            _userWithPosts.value = result
        }
    }

    // 监听全局用户数据更新(比如其他ViewModel修改了用户信息)
    fun observeUserUpdates() {
        userRepo.userUpdatedObservable
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { user ->
                // 刷新UI或触发其他逻辑
            }
            .addTo(CompositeDisposable()) // 用RxJava的Disposable管理,避免内存泄漏
    }
}
关键注意事项
  1. 严格遵守职责边界:
    不要让Repository再处理多请求组合逻辑,否则UseCase就失去了存在的意义,还是会回到原来的维护困境。UseCase是业务逻辑的唯一载体,Repository只负责数据的“获取”和“分发”。

  2. Subject的合理使用:
    Repository中的Subject应该只用于分发数据更新事件,而不是承载业务逻辑。比如用户数据更新后,所有订阅这个Subject的ViewModel都能收到通知,实现跨VM的数据同步。建议用BehaviorSubject(保存最新值,新订阅者能直接拿到)或PublishSubject(只发新事件),根据业务场景选择。

  3. RxJava与Coroutines的兼容:
    如果项目中同时使用两者,建议统一数据流类型。比如可以把Repository的Observable转换成Flow,用Coroutines的combine操作符替代Observable.zip,更贴合Kotlin生态:

    // Repository改用Flow
    fun getUser(userId: Int): Flow<User> {
        return flow { emit(api.getUser(userId)) }
            .onEach { userUpdatedFlow.emit(it) }
            .flowOn(Dispatchers.IO)
    }
    
    // UseCase用Coroutines实现
    class GetUserWithPostsUseCase @Inject constructor(private val userRepo: UserRepository) {
        suspend fun execute(userId: Int): Result<UserWithPosts> {
            return try {
                val user = userRepo.getUser(userId).first()
                val posts = userRepo.getUserPosts(userId).first()
                Result.success(UserWithPosts(user, posts))
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }
    
  4. 避免硬编码单例:
    用依赖注入(比如Dagger/Hilt)提供Repository的单例实例,而不是手动实现单例,这样测试时可以轻松替换Mock实例,提升代码的可测试性。

适配复杂业务的MVVM调整

传统MVVM示例往往只展示简单单请求场景,实际复杂项目中需要更清晰的分层:
UI层(Activity/Fragment) → ViewModel层(状态管理) → UseCase层(业务编排) → Repository层(数据获取) → 数据源层(网络/本地缓存)

这种分层让每个模块的职责明确,即使业务逻辑再复杂,也能把大问题拆解成小的、可维护的单元,降低维护成本。

内容的提问来源于stack exchange,提问作者Viktor Vostrikov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:01:56