为什么 Clean Architecture 能让 ViewModel 保持轻量?

作者:潜龙勿用之化骨龙日期:2026/6/5

在 Android 开发中,MVVM 是非常常见的 UI 架构模式。它通过 ViewModel 承接 UI 状态和用户交互,让界面层变得更加清晰。

但在真实项目里,随着业务不断增加,很多团队都会遇到同一个问题:

ViewModel 越写越胖,业务判断、网络请求、数据保存、异常处理全都堆在里面。

一开始这样写可能很方便,但当业务规则越来越复杂时,ViewModel 就会逐渐变成一个“业务大杂烩”:难维护、难复用、难扩展。

Clean Architecture 的价值,正是在于帮助我们把这些职责拆开,让 ViewModel 回到它真正应该负责的事情上:管理 UI 状态

本文通过一个“上传文件”的例子,说明 Clean Architecture 如何让 ViewModel 保持轻量,同时让业务逻辑和数据流职责更加清晰。


1. 问题示例:传统 MVVM 中的胖 ViewModel

假设有一个上传文件的需求,流程如下:

很多项目会直接把这些逻辑写在 ViewModel 中:

这段代码的问题在于,ViewModel 同时承担了太多职责:

  • 判断用户是否登录
  • 判断文件大小是否合法
  • 调用上传接口
  • 写入上传记录
  • 捕获异常
  • 转换 UI 状态

这些逻辑混在一起后,ViewModel 会越来越难维护。

今天只是检查登录和文件大小,明天可能还要加:

  • 普通用户和 VIP 用户上传大小不同
  • 某些文件类型禁止上传
  • 每日上传次数限制
  • 上传前需要风控校验
  • 上传成功后需要同步多个业务状态

如果这些规则都继续堆在 ViewModel 中,ViewModel 很快就会失控。


2. 先区分三类职责

这个上传流程看起来是一整段逻辑,但其实可以拆成三类职责:

职责示例所属层
UI 逻辑显示成功、失败、登录提示Presentation / ViewModel
业务逻辑未登录不能上传、文件大小限制、VIP 规则Domain / UseCase
数据流逻辑调接口、读写数据库、缓存上传记录Data / Repository

问题的根源通常不是 ViewModel 写了几行代码,而是:

不同层的职责混在了同一个类里。

Clean Architecture 要解决的,正是这种职责混乱的问题。


3. Clean Architecture 如何拆分上传逻辑?

Clean Architecture 的核心思想是:

让不同层负责不同职责,并且依赖方向保持单向。

一个常见的 Android 分层结构如下:

在这个结构中:

  • ViewModel 不直接处理业务规则
  • UseCase 负责业务流程和业务判断
  • Repository 负责数据获取、数据提交和数据持久化

4. ViewModel:只负责 UI 状态转换

重构后,ViewModel 不再关心“上传的业务规则是什么”,它只关心:

UseCase 返回了什么结果,我应该展示什么 UI 状态。

一种常见写法是让 UseCase 返回一次性的业务结果:

这样一来,ViewModel 的职责就非常清晰:

  • 接收 UI 事件
  • 调用 UseCase
  • 将业务结果转换成 UI 状态

它不再关心文件大小限制是多少,也不关心上传记录怎么保存。

不过,如果上传流程不是“一次请求、一次结果”,而是包含上传进度、任务状态、队列状态等持续变化,那么可以进一步使用 Flow + StateFlow


5. 更进一步:UseCase 暴露 Flow,ViewModel 使用 stateIn

对于上传这类场景,很多时候 UI 需要观察的不只是最终结果,还包括:

  • Idle
  • Loading
  • Progress
  • Success
  • Error

这种情况下,让 UseCase 暴露一条持续的状态流会更自然。

UseCase 可以负责维护上传过程中的业务状态:

然后 ViewModel 不再手动维护 _uiState,而是直接把 UseCase 的业务状态流转换成 UI 状态流:

这样 ViewModel 就只剩两件事:

这种写法对于 Compose 或基于 Flow 的页面非常友好:

UI 只需要订阅 uiState,状态变化会自动驱动界面更新。


6. UseCase:承载业务规则和业务流程

无论 UseCase 返回一次性的 UploadResult,还是暴露持续的 Flow<UploadResult>,它的核心职责都一样:

处理业务规则,编排业务流程,输出明确的业务结果。

例如:

UseCase 适合处理这些内容:

  • 业务规则判断
  • 多个 Repository 的协调
  • 业务流程编排
  • 将底层异常转换为领域结果
  • 输出明确的业务结果或业务状态流

例如后续需求变成:

这些规则都可以继续放在 UploadUseCase 中,而不是塞回 ViewModel。

这就是 ViewModel 不会变胖的关键。


7. Repository:专注数据访问,不承载业务规则

Repository 的职责是数据流,而不是业务规则。

Repository 负责:

  • 从接口获取数据
  • 向接口提交数据
  • 读写数据库
  • 管理缓存
  • 屏蔽具体数据来源

但它不应该负责:

  • 判断用户是否允许上传
  • 判断文件大小是否符合业务要求
  • 判断失败后 UI 应该展示什么

一个简单判断标准是:

如果这条规则来自产品需求,它通常应该放在 UseCase。
如果这段逻辑是在处理数据从哪里来、到哪里去,它通常属于 Repository。


8. 为什么返回 Result,而不是直接抛异常?

在业务开发中,失败通常可以分为两类。

业务失败

例如:

  • 用户未登录
  • 文件过大
  • 上传次数超过限制
  • 当前用户没有权限

这些失败是业务流程的一部分,不一定是异常。

系统异常

例如:

  • 网络错误
  • 服务端异常
  • 数据库写入失败
  • JSON 解析失败

这些才更接近真正的异常。

因此,UseCase 返回一个明确的 UploadResult,可以让调用方清楚知道这次操作的业务结果。

或者在持续状态场景中:

这样做有几个好处:

  • ViewModel 不需要理解底层异常
  • UI 状态转换更直观
  • 业务失败和系统异常边界更清晰
  • 业务结果可以被明确建模

9. 什么时候用 suspend Result,什么时候用 Flow?

这两种写法都可以,关键看业务场景。

如果是简单的一次性操作:

使用 suspend fun upload(file): UploadResult 就足够了。

如果业务过程存在连续状态:

或者需要页面持续观察:

那么使用 Flow<UploadResult>StateFlow<UploadResult> 会更加自然。

可以简单总结为:

场景推荐方式
一次请求,一次结果suspend fun(): Result
有进度、有队列、有持续状态Flow<Result> / StateFlow<Result>
UI 需要长期订阅状态ViewModel 使用 stateIn 转成 StateFlow

10. 总结

Clean Architecture 能让 ViewModel 保持轻量,核心原因是它把职责拆开了:

角色职责
ViewModel接收 UI 事件,暴露 UI 状态
UseCase处理业务规则,编排业务流程
Repository负责数据访问,屏蔽数据来源

对于上传文件这个例子来说:

  • ViewModel 不再判断能不能上传
  • UseCase 决定上传规则和业务结果
  • Repository 只负责真正的上传和记录保存

最终得到的代码结构会更加清晰:

如果业务是一次性的,UseCase 可以返回 suspend Result
如果业务状态是持续变化的,UseCase 可以暴露 Flow<Result>,ViewModel 通过 map + stateIn 转成 UI 可观察的 StateFlow

换句话说:

MVVM 主要解决 UI 层的组织问题。
Clean Architecture 解决的是业务逻辑和数据流的职责划分问题。

当业务越来越复杂时,这种分层能避免 ViewModel 不断膨胀,也能让业务逻辑更集中、更容易复用、更容易维护。


为什么 Clean Architecture 能让 ViewModel 保持轻量?》 是转载文章,点击查看原文


相关推荐


Git & Linux 速查表
love8888_cnsd2026/5/29

📋 Git & Linux 速查表 — Java 后端向 一、Git 操作速查 🔥 最高频(日常必用) 命令说明git status查看工作区状态(最常用,肌肉记忆)git add .添加所有改动到暂存区git add <file>添加指定文件到暂存区git commit -m "type: 描述"提交到本地仓库(推荐语义化 message)git push推送到远程仓库git pull拉取并合并远程更新(fetch + merge)git log --oneline -10查看最近 1


重构 AI 思维(一):Prompt Engineering,如何下达不可违抗的指令?
码上实战2026/5/7

嘿,兄弟们好,我是飞哥。 前阵子我发了那篇上岸感悟,很多兄弟私信我:“飞哥,你老说现在要靠 AI 铲子吃饭,可我发现这 AI 经常‘不听话’,给的回答不是太虚就是格式乱掉,这铲子不好使啊。” 确实,很多兄弟还把 AI 当成**“搜索引擎”在用——随手甩个问题,等着它给标准答案。但对于咱们要搞生产级应用的 Java 佬来说,你得把它当成一个“初级开发”或者“外包伙计”**。 你给外包下需求,如果只是随口一句“帮我实现个抢票逻辑”,他保准给你搞出一堆 Bug。你得有清晰的文档、明确的边界、严苛的格式


LuatOS 课程-011 讲:GNSS应用开发
上海合宙LuatOS2026/4/27

在物联网项目开发中,智能定位系统是一类常见且实用的应用场景,本文将基于 LuatOS,分享一款智能定位系统的开发思路与相关实现要点。 在实际开发过程中,类似学生卡定位器的需求十分普遍,这类需求通常对定位精度、设备续航能力、轨迹显示效果以及多平台适配性均有明确要求,而基于 LuatOS 的智能定位系统,可针对性解决这类开发需求中的核心痛点。 项目特点: 🛰️ 三合一定位:GNSS + 基站 + WiFi🔋 续航:智能功耗管理,运动才定位🛣️ 轨迹优化:减少80%GPS静态漂移以及运动漂移


OpenClaw 七大扩展组件深度技术解析
稚枭天卓2026/4/19

针对 Plugin、Skill、Tool、MCP、Agent、Command、Hook 七大核心组件进行底层实现维度的拆解。 1. Plugin (插件容器) 1.1 模块基础解析 核心定位: Plugin 是 OpenClaw 生态中的物理分发与逻辑隔离单元。它通过 openclaw.plugin.json 清单文件,将 Tool、Skill、Hook 等零散能力打包成一个可独立安装、版本控制和卸载的 NPM 包或本地目录。 使用场景: 开发者封装特定领域能力(如“飞书集成


M3-markconv库找不到wkhtmltopdf问题
郑恩赐2026/4/11

M3-markconv库找不到wkhtmltopdf问题 📝 摘要 在使用 markconv 进行 PDF 转换时,你可能会遇到 OSError: No wkhtmltopdf executable found 错误。这表示系统没有安装 wkhtmltopdf 工具,只需要安装它就能解决 💪 1. 问题描述 📚 1.1 主要报错 当你运行 PDF 转换代码时,会看到以下关键报错: OSError: No wkhtmltopdf executable found: "C:\\Progra


ToB架构师避坑指南:拒绝过度设计,用ROI思维构建高可用开放平台,一份设计指南
uzong2026/4/3

作者:面汤放盐 | uzong 本文将系统且全面地讨论如何设计一个开放平台,内容涉及布局、设计、踩坑及经验分享等。 面向群体:工程师、技术负责人、架构师等。 1. 开放平台 1.1. 定位清晰、MVP先行、ROI导向 需界定平台是专注于数据开放、能力开放,还是构建综合生态;同时明确目标用户群体,是服务大型企业、中小企业,还是特定行业客户 清晰的定位和画像直接决定了模块建设的优先级、功能深度及技术选型,是后续所有设计决策的基石,必须在方案初期落实。 在此基础上,应遵循 YAGNI(You Ain'


INFINI Labs 产品更新 - Easysearch 2.1.0 新增高性能 Rules 规则引擎插件,数据探索 Discover 等
极限实验室2026/3/25

INFINI Easysearch v2.1.0 发布:新增 Rules 规则引擎(百万级规则、复杂表达式、自动同步恢复)与 形态学分析插件(俄语/英语词形还原,提升搜索召回率);审计日志支持动态用户审计,UI 新增日志查看、配置及数据探索页面,运维更高效。INFINI Console、Gateway、Agent、Loadgen v1.30.3 统一基于 Framework 升级,优化本地磁盘队列数据消费。详情见 Release Notes。 Easysearch v2.1.0 INFINI E


AI辅助开发最佳实践:2026年新方法
牛奶2026/3/17

这是系列第六篇。05篇我们讲了AI批量处理,这篇来看看怎么系统化管理AI配置,让AI真正成为你的开发助手。 上一篇文章,我们讲了怎么用AI批量处理重复工作。 这篇文章,我们来聊聊怎么系统化管理AI配置。 原文地址 墨渊书肆/AI辅助开发最佳实践:2026年新方法 如果你已经用AI辅助开发一段时间,可能会遇到这些问题: 每次都要重复说同样的话 — "用TypeScript"、"注意暗色模式"、"用Tailwind" 好的实践没法传承 — 踩过的坑、学到的技巧,用完就忘了 团队配置不统一


小龙虾(openclaw),轻松玩转自动发帖
万少 VIP.5 如鱼得水2026/3/9

小龙虾(openclaw),轻松玩转自动发帖 万少:华为HDE、鸿蒙极客 个人主页:blog.zbztb.cn/ 2025年参与孵化了20+鸿蒙应用、技术文章300+、鸿蒙知识库用户500+、鸿蒙免费课程2套。 如果你也喜欢交流AI和鸿蒙技术,欢迎扣我。 前言 很多小伙伴说安装了小龙虾之后,不知道可以做一些什么,那么这里可以提供一些好玩的场景,比如自动发帖,这里主要通过稀土掘金来演示。 如果大家还没有安装好小龙虾(openclaw),可以参考这个内容先去安装,也可以联系我寻求帮助。 mp.


我的“龙虾”罢工了!正好对比下GLM、MiniMax、Kimi 3家谁更香
飞哥数智谈2026/3/1

OpenClaw(中文名:龙虾,曾用名:Clawdbot、Moltbot)从年底到现在热度持续飙升,而我感觉自己使用的还有些浅,没好意思写相关的内容。 结果今天我的龙虾罢工了,看了下,发现模型的 Coding Plan 到期了。 那正好了解下目前国内几家 Coding Plan 的内容,也方便大家对比。 本文仅涉及 GLM、Minimax、Kimi 3家自有产品的订阅套餐对比,像火山引擎、千问、无问芯穹类的综合套餐未加入对比。 为了方便对比,所有套餐都是采用按月 快速对比 核心指标整理到一

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读