在移动应用开发的实践中,ViewModel 的角色日益重要——它承载着 UI 状态、业务逻辑调用、生命周期管理等职责。然而,许多开发者的 ViewModel 常常越写越“胖”,动辄几百行甚至上千行代码,变成了难以维护的“神类”。与此形成鲜明对比的是,采用 Clean Architecture(整洁架构) 的团队,却能轻松将 ViewModel 保持在极轻量、高度可读的状态。这背后究竟有何玄机?本文将从架构设计的角度为您深度解析。

一、ViewModel 的“发福”困境

在传统 MVC 或简单 MVVM 模式下,ViewModel 常常被迫承担超出其本分的职责。比如直接调用网络请求、解析 JSON、处理本地数据库、执行缓存策略、进行日期格式化、甚至写日志……这些本应分属不同抽象层的任务,全部挤在 ViewModel 里。一旦需求变化,ViewModel 便成了牵一发而动全身的“重灾区”。

更糟糕的是,这种“胖子 ViewModel”往往难以测试——因为内部依赖了数据库、网络、文件系统等具体实现,单元测试时需要大量 Mock,测试代码甚至比业务代码还复杂。

二、Clean Architecture 的分层魔法

Clean Architecture 的核心原则是依赖规则(Dependency Rule):源代码的依赖关系必须指向内层,内层不依赖于外层。它通常分为三层:

  • 外层(Data Layer):网络接口、数据库、本地存储等具体实现。
  • 中间层(Domain Layer):业务逻辑、用例(Use Cases)、实体模型。
  • 内层(Presentation Layer):UI、ViewModel、状态管理等与视图相关的部分。

在这种分层下,ViewModel 只属于最内层(Presentation Layer)。它唯一的职责是:协调 UI 状态与交互事件。具体来说,ViewModel 通过依赖注入获取所需用例(Use Cases),然后调用这些用例获取数据,再将数据转换为 UI 可观察的状态对象(如 StateFlow 或 LiveData)。它不必知道数据从何而来——是网络还是缓存,由外层决定。

三、轻量背后的三个关键机制

1. 用例(Use Cases)承担业务逻辑

在 Clean Architecture 中,所有具体的业务操作——比如“获取用户订单列表”、“验证登录密码”、“计算折扣后价格”——都被封装在 Use Case 对象里。这些 Use Case 是领域层的核心,它们只关注业务规则,不依赖任何 Android 或 Swift 框架。ViewModel 调用一个 Use Case 就像调用一个方法:输入参数、返回结果。因此 ViewModel 中几乎没有任何条件判断、循环、数据转换等复杂逻辑,代码自然变得单薄。

2. 数据源解耦:ViewModel 不必过问“数据在哪”

传统 ViewModel 常会写类似 repository.fetchData() 这样的代码,而 repository 内部可能又调用了网络和数据库。但在 Clean Architecture 中,ViewModel 操作的只是 Use Case,而 Use Case 内部通过接口(抽象)依赖外层的数据仓库。具体的数据获取策略(先读缓存还是先请求网络)完全由外层实现决定。ViewModel 只关心“给我数据”,至于数据如何来,它绝不越权。

3. 状态管理独立:ViewState 模式

轻量 ViewModel 的另一个常见实践是采用 ViewState 模式:ViewModel 只暴露一个不可变的状态对象(如 sealed class 或 data class),UI 根据状态渲染。所有的合并、映射、错误处理等逻辑,要么由 Use Case 返回,要么在 ViewModel 内部通过简单的映射语法(如 map)完成,而非通过复杂的 switch-case 或临时变量。这使得 ViewModel 的方法数量极少,往往只有几个 fun 和几个 StateFlow

四、真实场景对比

我们可以做一个简单对比:没有 Clean Architecture 时,ViewModel 中可能包含 fetchData() 方法,里面写有 try-catchCoroutineScope 管理、数据转换、缓存判断、UI 状态更新等。而采用 Clean Architecture 后,ViewModel 可能是这样的:

class MyViewModel(private val getMyDataUseCase: GetMyDataUseCase) : ViewModel() {
    private val _state = MutableStateFlow<MyViewState>(Loading)
    val state: StateFlow<MyViewState> = _state

    fun load() {
        viewModelScope.launch {
            _state.value = try {
                Success(getMyDataUseCase())
            } catch (e: Exception) {
                Error(e.message ?: "未知错误")
            }
        }
    }
}

除去必要的模板代码,实际业务代码仅有两三行。所有复杂的数据获取、转换、重试策略都被隐藏在 Use Case 内部。这样的 ViewModel 即便在项目规模膨胀后,依然保持纤瘦。

五、轻量的价值:不止于代码行数

ViewModel 保持轻量带来的直接好处包括:

  • 可测试性:ViewMode 只依赖抽象的 Use Case,测试时只需 Mock 用例,无需搭建数据库或网络环境。
  • 可维护性:一个 ViewModel 文件通常不超过 100 行,新人接手时能迅速理解其职责。
  • 复用性:Use Case 可以被不同的 ViewModel 甚至不同平台共享,避免了重复代码。
  • 可替换性:当 UI 层从 Compose 迁移到 SwiftUI 或 React Native 时,ViewMode 只需调整状态转换,核心业务逻辑纹丝不动。

六、结语

Clean Architecture 并不是银弹,但它提供了一种清晰的边界:让 ViewModel 专注于“呈现与交互”,让其他层各司其职。正是这种强制性的职责分离,使得 ViewModel 能够从臃肿走向轻量。对于正在攻克代码质量的团队来说,理解并践行这一理念,远比手动“减肥”一座千行 ViewModel 更有效。毕竟,架构设计的目的从来不是增加复杂度,而是让每个模块都只做自己该做的事——不多不少,刚刚好。