稀有猿诉

十年磨一剑,历炼出锋芒,说话千百句,不如码二行。

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

本文译自「Yes, That’s MVI: The Pattern’s Full History, Misconceptions, and Modern Android Form」,原文链接https://proandroiddev.com/yes-that-is-mvi-674f810ca4fe,由Eury Pérez Beltré发布于2025年5月29日。

每项传统背后都有其原因——而原因背后,又蕴藏着一个故事。 – 佚名

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

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

目录

  1. 引言:超越流行语

  2. 架构先驱:MVI 的雏形

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

  4. 问题:MVI 的误解

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

  6. 结语:我的个人见解

  7. 引言:超越流行语

MVI 并没有像其他设计模式一样在我们可以参考的论文或书籍中进行介绍。这是其创建者深思熟虑的结果,他对当时的现有解决方案提出了异议(123)。

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

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

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

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

来源:cycle.js.org

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 引入了一种新的架构来管理数据流:

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

来源:reactjs.org

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

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

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

  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 内的化简器函数之类的更改可以证明定义不同的模式是合理的。
  • 是的,你可以调整/个性化模式,但仍然避免告诉其他人你的实现是“真实的”或“最佳”的。
  • 历史很重要。许多声称 MVI 受到 Redux 启发的人可能甚至没有意识到 MVI 首先存在
  • 最后,MVI 实现不得 包含像 Redux 这样的全局存储。这样做意味着它不是 MVI。 (是的,我正在看着你,MVI 警察 - 加油!🚓)
  • MVI 创建者一开始甚至不喜欢 Redux
  • MVI 简单明了。 Redux 没那么多……

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

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

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

来源:

Comments