MVI模式的完整历史、误解和现代Android范式

本文译自「Yes, That's MVI: The Pattern's Full History, Misconceptions, and Modern Android Form」,原文链接proandroiddev.com/yes-that-is...,由Eury Pérez Beltré发布于2025年5月29日。
每项传统背后都有其原因------而原因背后,又蕴藏着一个故事。 -- 佚名

欢迎阅读本文!我的目标是帮助你真正理解 MVI 模式的本质,解释我为何对我的观点充满信心,以及该模式的起源。但在你深入阅读之前,请注意:

📝 本文包含对 MVI 架构模式的历史和演变的广泛而深入的研究,以及作者的观点。请做好区分事实和观点的准备 😉

目录

  1. 引言:超越流行语

  2. 架构先驱:MVI 的雏形

  3. MVI 的起源:模式的形成

  4. 问题:MVI 的误解

  5. Android 中的 MVI:它的真实面貌

  6. 结语:我的个人见解

  7. 引言:超越流行语


MVI 已成为 Android 开发中一种强大的架构模式,它在管理状态和 UI 复杂性方面提供了清晰性和可预测性。如果运用得当,它可以提供一个强大的思维模型,帮助开发者构建可维护和可扩展的应用程序。

然而,我称之为"MVI警察"的那部分人坚持认为,只有严格遵循MVU或Redux风格的实现才算真正的MVI,并将其他有效的方法斥为贴错标签。这种狭隘的观点忽略了MVI的真正起源,以及它在不丧失核心原则的前提下,如何灵活地适应不同的应用场景。

在本文中,我们将探讨MVI的起源、它旨在解决的问题,以及如何通过秉持其精神(而非僵化的规则)来帮助开发者编写简洁高效的架构。毕竟,优秀的架构在于解决问题,而非墨守成规。

  1. 架构先驱:导致 MVI 的模式

MVI 并没有像其他设计模式一样在我们可以参考的论文或书籍中进行介绍。这是其创建者深思熟虑的结果,他对当时的现有解决方案提出了异议(12(staltz.com/nothing-new... except-one-thing)、 3(staltz.com/uni Direction-user-interface-architectures/#:~:text=Model-Vie))。

虽然许多人认为 MVI 是 Redux (2015) 或 Elm 的 MVU (2011) 的直接后代,但它真正的概念根源在于两种早期模式:模型-视图-控制器 (MVC)Flux,根据 André Staltz(cycle.js 和MVI 模式)。

模型-视图-控制器 (MVC) --- 大约 1979 年

作为最早且最有影响力的架构模式之一,MVC 将应用程序分为三个主要组件:

  • 模型: 管理数据和业务逻辑
  • View: 负责渲染UI元素
  • 控制器: 处理用户输入并相应更新模型

André Staltz 通过 反应式编程 重新审视了 MVC。在他 2014 年的博客文章 "Reactive MVC and the Virtual DOM" 中,他提出了针对基于流的 UI 定制的 MVC 的现代重新思考。在这个重新想象中:

  • 意图 取代了控制器:它将用户交互捕获为事件流
  • 模型 对这些流作出反应以更新应用程序状态。
  • View 反应性地呈现状态。

他将这种重组后的架构称为模型-视图-意图 (MVI),这使其成为该模式的首次正式提及。这个版本的 MVC 尊重单向数据流,同时与 RxJS 等基于可观察的系统自然地保持一致。

Flux (MVC) --- 2014 年 5 月

在 Staltz 发表博文之前不久,Facebook 推出了 FluxReact。 React 专注于 UI 渲染,而 Flux 引入了一种新的架构来管理数据流:

  • 动作描述发生了什么。
  • 调度程序 将操作路由到商店。
  • 商店 保存应用程序状态并更新以响应操作。
  • 视图 侦听存储更改并重新渲染。

Flux 最重要的创新是单向和循环数据流 ,它为 UI 应用程序中共享可变状态的混乱带来了秩序。 Staltz 认可了这一创新,但批评 Flux 经常混合命令式反应式范例。尽管如此,保持状态变化可预测和线性的原则仍然成为 MVI 的关键部分。

在MVI中,Staltz采用了Flux的单向数据流 ,但对其进行了简化。他删除了 Dispatcher,避免了命令式存储,而是使用 observables 将用户意图、状态和 UI 渲染直接建模为纯粹的反应式转换。用他自己的话说:

"React/Flux 组合显然受到了响应式编程原则的启发,但 API 和架构是交互式和响应式模式的不合理组合......我们可以做得更好。"

  1. MVI 的起源:模式是如何形成的

MVI 模式由其创建者 André Staltz 于 2014 年左右在 javascript 框架 Cycle.js 中引入,一年前Redux 诞生了。

MVI 模式的实践

模型-视图-意图 (MVI) 是一种反应式 UI 架构模式,它强制执行单个、不可变的_状态_(模型 )、Intents (用户操作)进入系统的单向流,以及渲染每个状态的 视图

  • 模型 保存应用程序状态。
  • View 呈现状态。
  • 意图 取代控制器作为用户交互流。

你可以直接从其创建者在 2015 年写的有关架构模式的博客文章中阅读 MVI 及其组件的定义。

Flux 对不可变状态和单向流的关注影响了 MVI 的设计,它使流程形式化:用户意图 → 状态更改 → UI 渲染。这种来自经典 MVC、Flux 和反应式编程原理的思想的综合催生了 MVI------不是作为 Redux(后来出现)或 Elm 的衍生品,而是作为一种为反应式系统量身定制的独特架构。

代码示例

在一篇博客文章中,Hannes Dorfmann 解释了 André Staltz 关于 MVI 的最早想法,并将这个想法表达为数学表达式:

这个表达式非常适合理解 MVI 组件如何交织在一起并表示相互之间的依赖关系:意图 被传递到 模型 ,它生成一个新的 状态 ,并提供给 视图 来呈现它。这个数学表达式浓缩了MVI的精神。

Cycle.js 的实现类似于:

你可以看到这个数学表达式按字面意思表示在第 44-46 行中。

这看起来不错,但这个想法实际上如何转化为 Kotlin 呢?

好吧,别那么快,我们会谈到这一点。现在,让我们继续我们的揭幕之旅......

  1. 问题:MVI 误解

随着 MVI 模式在 Android 生态系统中越来越流行,特别是随着 Jetpack Compose、协程和单向状态管理的兴起,出现了一股怀疑浪潮。一些开发者(被戏称为"MVI 警察")开始质疑许多 Kotlin/Android 实现是否可以真正被称为"MVI"。

MVI 和 Redux 的误解

最常见的误解之一是 MVI 只是 Redux,但名称不同 ,或者 MVI 直接受到 Redux 的启发。但正如你在前面的部分中所看到的,这在历史上并不准确 :MVI 是由 André Staltz 于 2014 年在他的 Reactive MVC 博客文章中引入的,Redux 发布前几个月 较晚发布2015 作者:丹·阿布拉莫夫。

也就是说,很容易理解混乱的根源。 MVI 和 Redux 确实有几个共同的核心思想------但这并不是因为 MVI 源自 Redux。相反,两者都独立地受到相同底层架构的影响:Flux

这些相似之处包括:

  • MVI 对待 用户意图 的方式类似于 Redux 操作
  • MVI 模型状态 是不可变的并且随着时间的推移而演变,就像 Redux 的 store 一样。
  • MVI 使用 reducers 从先前的状态和用户输入导出新的状态。

然而,在这些相似之处中存在着关键的架构差异

现在你应该同意 Kotlin/Android MVI 实现不需要 严格遵循 Redux 设计模式。

MVI 和 MVVM 的误解

另一个常见的误解是,许多流行的 MVI 实现只是 MVVM,具有单个状态数据类和用于意图的密封接口。为了澄清这一点,让我们首先回顾一下设计模式真正是什么的基础知识。

根据 Gang of Four 于 1994 年出版的开创性著作《设计模式:可重用面向对象软件的元素》:

"设计模式系统地 命名、激发并解释了解决面向对象系统中重复出现的设计问题的通用设计。"

"设计模式是针对软件设计中常见问题的通用可重复解决方案 。它不是可以直接转换为代码的最终设计,而是如何解决问题的描述或模板,可以在许多不同的情况下使用。"

就像自然界中发现的模式一样------无论是数字、视觉还是其他------突变或修改现有模式通常会产生新的、独特的模式:

那么,追随MVI趋势的开发者实际上是在无意识地做MVVM吗?为了解决这个问题,让我们比较两种模式之间的异同,就像我们在上一节中所做的那样。

为了保持观点的基础,我们将引用一篇备受推崇的 Microsoft 文章 --- MVVM早在2005年。

两种模式都有几个共同特征:

  • 明确 关注点分离
  • 使用可观察流来更新 UI
  • ViewModels 的使用(尤其是在 Android 上)

然而,当我们并排检查细节时,我们会发现,尽管有这些表面上的相似之处,MVI 和 MVVM 在关键方面却存在根本性的不同------展示了模式的演变如何导致全新的架构方法。

正如软件开发经常告诉我们的那样,乍一看相似的东西一旦仔细观察就会发现明显的差异。

Android 中干净简单的 MVVM 实现如下所示:

Android 团队不久前创建的这个图表支持了这个想法,该图表旨在为应用程序建议一种架构模式:

请注意 viewModel 中的多个 LiveData 实例。现在要查看 MVI 示例,让我们跳到下一部分。

  1. Android 中的 MVI:它到底是什么样子?

经过深入研究并与相关模式进行比较后,我们可以自信地确定定义真正的 MVI 实现的核心特征:

  • 单个不可变状态对象: 该状态是分散的,通常驻留在 ViewModel 中。
  • 作为意图的 UI 交互: 所有用户操作都表示为数据流,通常是密封的类或接口。
  • Reducer 函数: 一个纯函数,它接受先前的状态和一个 Intent,然后返回一个新的 State(Old State + Intent → New State)。
  • 单向数据流: 该架构严格遵循"意图→模型→状态→视图"的流向。
  • 单独处理的副作用: 诸如导航、网络调用或显示消息之类的效果被明确建模并在减速器外部进行处理。

我已经听到了一些反对意见,但让我们把理论放在一边,看看代码中的样子。

MVI 组件作为模型

对于我们的示例,我们将定义三个基本组件:

  • **UserUiState**:作为 Kotlin Flow 公开的不可变状态模型。
  • **UserIntent**:表示 UI 中所有可能的用户操作。
  • **UserSideEffect**:定义纯状态更新逻辑之外发生的副作用。

MVI 中的 ViewModel 角色

在 Android MVI 功能中,ViewModel 负责:

  • 状态暴露为不可变的、可观察的流。
  • 副作用暴露为单独的不可变流。
  • 提供一个公共函数来接受和处理Intent,应用reducer逻辑来相应地更新状态。

正如你现在可能已经意识到的那样,这与一些人批评为"不是真正的 MVI"的实现非常相似。但本文中汇编的历史背景和证据表明事实并非如此:社区一直在做正确的事情。也许 MVI Police 是时候重新审视他们自己对该模式的定义了。

📝 注意:我不推荐这种副作用实现,但出于教育目的,我会保持简单。阅读我的文章了解一次性事件的最佳实践,以获得有关此主题的深入解释。

  1. 最后的想法:我个人的看法

尽管我从未认同"MVI警察"的思维方式,但我意识到我缺乏可靠的信息来支持我的观点。这次广泛的调查教会了我一些宝贵的经验教训------有些甚至不是关于设计模式或软件工程的。我想与大家分享一些:

  • 在软件开发和生活中,对你来说听起来合乎逻辑并不总是正确的。
  • 概念有历史并随着时间的推移而演变;认识到这一过程至关重要。
  • 在仅仅因为_你_相信你的方式是唯一正确的方式而告诉别人他们错了之前,三思而后行------有时甚至三思而行。多种有效方法可以共存。

在技术方面,最大的收获实际上是关于软技能:

  • 设计模式是常见问题的重复解决方案。如果该解决方案发生重大变化,它就会成为一种_新_模式。
  • 是的,诸如使用单个不可变数据类用于状态、用于意图的密封接口以及 ViewModel 内的化简器函数之类的更改可以证明定义不同的模式是合理的。
  • 是的,你可以调整/个性化模式,但仍然避免告诉其他人你的实现是"真实的"或"最佳"的。
  • 历史很重要 。许多声称 MVI 受到 Redux 启发的人可能甚至没有意识到 MVI 首先存在
  • 最后,MVI 实现不得 包含像 Redux 这样的全局存储。这样做意味着它不是 MVI。 (是的,我正在看着你,MVI 警察 - 加油!🚓)
  • MVI 创建者一开始甚至不喜欢 Redux
  • MVI 简单明了。 Redux 没那么多......

归根结底,真正重要的是理解这些模式背后的原因,尊重它们的演变 ,并深思熟虑地应用它们解决实际问题 。让我们拥抱想法的多样性,而不是遵循"正确的方式"。这种方法将促进更健康的讨论并最终带来更好的软件。

在 Medium 和 LinkedIn 上关注我,了解我何时撰写这篇文章,向你展示我个人如何在项目中利用 MVI。

如果这篇文章对你有帮助,请点赞、评论、分享😉

来源:

欢迎搜索并关注 公众号「稀有猿诉」 获取更多的优质文章!

保护原创,请勿转载!

相关推荐
心中有国也有家7 小时前
AtomGit Flutter 鸿蒙客户端:Canvas 绘制进阶-路径、渐变与混合模式
android·javascript·flutter·华为·harmonyos
hunterandroid8 小时前
WorkManager 可靠性实战:唯一任务、重试与幂等设计
android·前端
程序员老刘8 小时前
Android 17的10个AI功能,国内用户真正能用的有几个?
android·flutter·ai编程
花开彼岸天~9 小时前
鸿蒙应用开发实战【94】— 实战多卡号场景管理最佳实践
android·华为·harmonyos·鸿蒙系统
用户41659673693559 小时前
视频源文件音频轨异常分析记录
android
我命由我1234510 小时前
集成 mupdf-android 的示例做为模块使用报错,程序包android.support.v4.view不存在
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
雨白12 小时前
嵌套 ScrollView 滑动冲突实战:实现内部优先滚动
android
阿pin12 小时前
Android随笔-IntentService详解(了解)
android·intentservice
海天鹰13 小时前
content://com.android.externalstorage.documents/document/primary%3A
android