第3章 分层架构中的ViewModel位置:UI层与领域层之间的中介

3.1 边界定义

在经典的分层架构中,ViewModel 明确位于 UI 层与领域层之间,承担着“中介”角色。它不属于 UI 层(不持有任何视图引用),也不属于领域层(不包含业务规则),而是独立存在于所谓的“表示逻辑层”或“应用层”。这个边界的核心原则是:

  • UI 层只与 ViewModel 交互,绝不直接访问领域层。
  • ViewModel 只与领域层(通过接口或抽象)交互,不依赖 UI 具体实现。
  • 领域层对上层完全无知,不引用 UI 或 ViewModel 的任何类型。
边界定义公式:
UI → ViewModel (接口/抽象) → 领域层 (接口) → 领域实现
反向依赖箭头从不允许出现。

3.2 分层策略

推荐采用 四层结构(Presentation / Application / Domain / Infrastructure),其中 ViewModel 归属于 Application 层顶部,与 Use Case / Service 同层但职责不同。

层次 包含内容 依赖方向
Presentation(UI) Activity / Fragment / ViewController / Compose 屏幕 依赖 Application 层(ViewModel 接口)
Application(ViewModel) ViewModel 实现、状态容器、Use Case 编排 依赖 Domain 层(Repository 接口)
Domain 实体、值对象、领域服务、Repository 接口 无依赖(纯净业务规则)
Infrastructure 数据库、网络、文件系统实现 实现 Domain 接口,不反向依赖

3.3 组件职责

  • UI 层:仅负责渲染状态、收集用户事件并转发给 ViewModel。不执行任何数据转换或业务判断。
  • ViewModel:管理屏幕级状态(State)、处理用户动作(Event)、调用领域层获取/提交数据、执行轻量级表示逻辑(如格式化、合并数据)。
  • 领域层:封装核心业务规则、实体行为、数据完整性校验。完全不关心状态如何展示。
关键判定标准: 如果在 ViewModel 中发现了“订单总价不能为负数”这类规则,说明领域逻辑泄漏到了 ViewModel。此类规则应下移到领域实体或领域服务中。

3.4 数据流 / 控制流 / 信任链路

分层架构通过强制单向依赖来建立信任链路:

  1. 数据流(下行):领域层 → ViewModel → UI。ViewModel 从领域层获取数据,转换成 UI 可观察的状态流(如 StateFlow / LiveData)。
  2. 控制流(上行):UI → ViewModel → 领域层。用户点击事件通过 ViewModel 转换为领域命令(Command)或查询(Query)。
  3. 信任链路:UI 信任 ViewModel 提供的数据格式正确;ViewModel 信任领域层返回的数据已通过业务规则校验;领域层信任基础设施提供的数据持久化能力。每一层只信任直接下一层,不跨层信任。
分层架构中ViewModel位置及数据流 分层架构中 ViewModel 位置与数据流 Presentation Layer(UI层) Activity / Fragment / Composable / ViewController Application Layer(ViewModel层) ViewModel / StateHolder / UseCase 编排 Domain Layer(领域层) 实体 / 值对象 / 领域服务 / Repository 接口 Infrastructure Layer 数据流(下行) 控制流(上行) 单向依赖:UI→ViewModel→领域(接口)→基础设施;领域层不依赖上层

3.5 选型权衡

优势 代价
UI 与领域完全解耦,可独立替换/测试 每屏需要额外 ViewModel 类,项目文件增多
领域层保持纯净,便于复用和单元测试 简单 CRUD 场景可能过度抽象
状态集中管理,生命周期安全 需要依赖注入框架管理 ViewModel 及其依赖
团队可以并行开发(UI 开发用 mock ViewModel) 新手容易将领域逻辑误放入 ViewModel

3.6 风险点

  • ViewModel 膨胀: 状态和事件过多导致单个 ViewModel 代码量过大。应当拆分为多个专注的 ViewModel 或使用协程/Flow 进行组合。
  • 领域逻辑泄漏: 将实体方法直接复制到 ViewModel 中。团队需通过 Code Review 和静态分析约束。
  • 过度分层: 对于极简单的列表-详情场景,强制使用 ViewModel 反而增加复杂性。建议在架构规范中允许“快捷路径”(如直接使用 Repository + LiveData),但要声明为例外并明确重构时机。
常见反模式: ViewModel 中调用 Infrastructure 层(如直接访问 OkHttp 或 Room)。正确的做法是 ViewModel → Domain 接口 → Infrastructure 实现。跳过领域层将导致接口不稳定,且无法切换数据源。

3.7 落地步骤

  1. 定义领域层接口: 在 Domain 模块中声明 Repository 接口和 UseCase 类。
  2. 创建 ViewModel 类: 在 Application 模块中编写 ViewModel,通过构造函数注入 Repository 接口。
  3. 注册依赖: 在 DI 容器(如 Hilt / Koin)中将 ViewModel 与 UI 绑定,并实现 Repository 接口与 Infrastructure 实现的映射。
  4. 实现数据流管道: ViewModel 使用 Flow / LiveData 暴露状态,UI 层通过 collect / observe 订阅。
  5. 编写单元测试: 对 ViewModel 注入 mock Repository,验证状态转换正确性。
  6. 集成测试: 检查 UI 层是否通过 ViewModel 间接访问领域层,确保无直接依赖。

3.8 验收标准

验收项 检查方法
UI 层无领域层引用 查看 UI 模块的 import,不应包含 Domain 实体类或 Repository 接口(仅可引用 ViewModel 接口/抽象)
ViewModel 不包含领域规则 检查 ViewModel 方法中无 if-else 业务判断;所有业务规则应委托给领域服务或实体
领域层可独立运行(无运行时依赖上层) 使用单纯 JUnit 测试领域实体,不需加载 Android 框架或 DI 容器
替换 UI 技术栈不影响领域层 从 Compose 切换到 View System 只需重写 UI 层和 ViewModel 的适配代码,领域层完全不变
ViewModel 可被单独测试 不启动 Activity,通过 mock Repository 即可执行 ViewModel 的单元测试