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管理,避免内存泄漏 } }
严格遵守职责边界:
不要让Repository再处理多请求组合逻辑,否则UseCase就失去了存在的意义,还是会回到原来的维护困境。UseCase是业务逻辑的唯一载体,Repository只负责数据的“获取”和“分发”。Subject的合理使用:
Repository中的Subject应该只用于分发数据更新事件,而不是承载业务逻辑。比如用户数据更新后,所有订阅这个Subject的ViewModel都能收到通知,实现跨VM的数据同步。建议用BehaviorSubject(保存最新值,新订阅者能直接拿到)或PublishSubject(只发新事件),根据业务场景选择。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) } } }避免硬编码单例:
用依赖注入(比如Dagger/Hilt)提供Repository的单例实例,而不是手动实现单例,这样测试时可以轻松替换Mock实例,提升代码的可测试性。
传统MVVM示例往往只展示简单单请求场景,实际复杂项目中需要更清晰的分层:UI层(Activity/Fragment) → ViewModel层(状态管理) → UseCase层(业务编排) → Repository层(数据获取) → 数据源层(网络/本地缓存)
这种分层让每个模块的职责明确,即使业务逻辑再复杂,也能把大问题拆解成小的、可维护的单元,降低维护成本。
内容的提问来源于stack exchange,提问作者Viktor Vostrikov

