当前位置: 首页 > news >正文

UI架构的历史与基础入门

本笔记的目的是通过一系列连贯的例子来探讨“事物-模型-视图-编辑器”这一隐喻。

这些例子都来自我的规划系统(planning system),用于解释上述四个概念。所有例子都已实现,但并未在本文描述的清晰类结构中实现。

这些隐喻对应于《关于DynaBook需求的笔记》中提出的现实世界-模型-视图-工具(Thing-Model-View-Tool)的概念。

——摘自1979年挪威数学家Trygve Reenskaug的笔记

什么是架构?

事实上,从韩国、中国、美国到西方的案例可以看出,大多数情况下,代码结构被用作架构的代名词。然而,正因为如此,初学者很容易对编程架构感到困惑。

尽管关注点的范围和焦点不同,但由于它们经常在同一层级上混用,因此容易引发混淆。

也就是说,诸如结构架构、UI架构、流程控制等具有不同关注点和范围的概念被当作平等的内容讨论。

无论是在韩国、中国、西方还是日本,这种现象经常出现在所谓的资深开发者中。

那么,为了便于理解,简单来说,

架构是什么?

架构是指软件系统中的高层次设计,它定义了系统的组成部分以及这些部分之间的关系和交互方式。架构的核心目标是解决特定问题,同时满足系统的功能性需求和非功能性需求(如可扩展性、可维护性和性能)。根据不同的关注点,架构可以分为以下几类:

架构分类与示例

类别

描述

代表性架构

整体系统结构

层级依赖方向、领域隔离、业务核心保护

Clean Architecture, Hexagonal (Ports & Adapters), Onion, Layered (3-tier)

UI结构与视图流架构

视图-逻辑-模型间的数据流、用户事件处理

MVC, MVP, MVVM, MVI, VIPER, Redux

内部逻辑控制架构

状态转换/消息驱动的控制逻辑

FSM, Saga, Workflow, RuleEngine

部署与基础设施架构

物理部署/通信结构

Microservices, Monolith, Serverless, Service Mesh

各类架构的主要目标通常如下:

  • 系统结构架构 → 控制依赖方向、提升测试便利性、保护核心业务逻辑。
  • UI结构架构 → 整理用户输入 → 状态变更 → 渲染流程。
  • 行为控制架构 → 内部状态与流程控制。
  • 部署架构 → 根据网络/服务分布设计物理结构。

本文主要讨论的是UI结构,即视图架构(View Architecture)

视图架构是什么?

视图架构是一种专注于如何连接视图与逻辑的局部架构模式。

它与实际的领域逻辑、基础设施或部署无关,是整体系统架构的一部分。

该架构的起源可以追溯到将UI视为机器的尝试,其核心在于如何理解单向状态流。

(Trygve Reenskaug)

MVC(Model - View - Controller)

视图架构(以下简称架构)的起源可以追溯到挪威数学家Trygve Reenskaug在施乐帕洛阿尔托研究中心(Xerox PARC)开发Smalltalk GUI系统时首次提出的概念。

其目的是将用户界面(UI)逻辑与领域逻辑分离。

最初的名称是TMVE(Thing-Model-View-Editor),但后来经过多次演变,最终形成了MVC(Model-View-Controller)模式。MVC通过马丁·福勒(Martin Fowler)的经典著作《企业应用架构模式》(Patterns of Enterprise Application Architecture)等广泛传播。(虽然他不是最初的发明者,但在推广方面做出了巨大贡献。)

MVC的构成要素如下:

构成要素

角色

说明

Model

数据状态管理

包含应用程序的核心逻辑和状态,例如数据库、内存状态、业务规则等。View和Controller引用或操作Model。

View

视觉显示

负责向用户展示Model的状态。它负责屏幕上的信息、布局和样式,自身的逻辑最少化。

Controller

用户输入处理

接收用户输入(如按钮点击、键盘输入等),操作适当的Model,并请求更新View。充当View和Model之间的中介角色。

MVC最具影响力的案例之一是基于Java的Spring框架(2002年至今)。

尽管Spring仍然被广泛使用,但实际上许多最初基于MVC的框架已经发生了很大的变化。例如,虽然Spring被称为MVC框架,但如今的Spring已经有了很大的演变:它不再将View视为Java的一部分,甚至可以在没有View的情况下仅保留Controller。此外,Controller也逐渐演变为更像是HTTP处理器的角色,而非传统意义上的控制器。因此,MVC在某种程度上已经成为一种“品牌化”的概念。

除此之外,AngularJS和React等框架也被归类为MVC,但实际上它们并不是传统的MVC,而是变体:

  • AngularJS更接近于MVVM模式,其中Controller扮演ViewModel的角色,并支持双向绑定,使得View和Model之间的界限变得模糊。
  • React则并非MVC,而是基于单一View的设计,与传统MVC相去甚远。

那么,什么时候应该使用MVC?

MVC适用于结构简单、视图和逻辑相对明确分离的情况,特别是在以服务器端渲染为中心的架构中非常适合。

然而,随着现代架构设计的多样化发展,MVC更适合用于小规模个人项目,而不是大型复杂系统。

传统的小型对话(Smalltalk)控制器功能被提升到了应用层面,
同时考虑了中间过程的选择(selection)、命令(command)和交互(interactor)等概念。为了捕捉这一差异,我们将这种类型的控制器称为“Presenter(展示器)”。因此,我们将这种编程模型整体称为Model-View-Presenter(MVP) ,并承认它是MVC的一种泛化形式。

——摘自1996年Taligent文档

MVP (Model - View - Presenter)

MVC有一个问题:Controller在处理View事件和Model时承担了过多的职责,导致所谓的“Fat Controller”问题。

此外,View和Controller之间的耦合性较强,事件循环与UI框架紧密结合,使得测试变得困难。

最终,为了解决这些问题:

引入了一个新的中介角色——Presenter,并将View抽象为接口,从而可以在Presenter中进行测试。

MVP的构成要素如下:

构成要素

角色

说明

Model

状态及数据处理

负责处理应用程序的状态和数据,包括数据存储、业务规则应用、外部API调用等纯粹的业务逻辑层,与UI分离。

View

UI呈现及用户输入传递

负责向用户显示UI并将输入传递给Presenter。View应设计为尽可能简单的“Dumb View”,不包含复杂的逻辑或状态处理,通常通过接口与Presenter连接。

Presenter

接收View事件 → 操作Model → 将结果反馈给View

接收来自View的用户事件,操作Model,并将结果返回给View。充当View和Model之间的中介,负责调整数据流和管理流程,同时通过接口松散依赖于View,以便于单元测试。

MVP在2010年代初期被广泛应用于Android开发中。

原因是Activity/Fragment结构承担了过多的职责,因此作为最佳实践,将其职责分离到Presenter中。

  • View : Activity/Fragment(实现View接口)
  • Presenter : 向View传递结果
  • Model : Repository、UseCase等

以这种方式,许多项目采用了MVP架构。

不过,目前只有在使用.NET的WinForm时才倾向于采用MVP架构。
这是因为UI事件循环与UI对象紧密绑定,而通过抽象化可以更方便地进行单元测试,因此MVP成为了一个常见的选择。

MVP主要用于客户端应用程序,但随着技术发展,许多框架逐渐转向MVVM架构。

那么,什么时候应该使用MVP?

当复杂UI事件处理非常重要且需要高测试便利性的移动应用时,MVP是一个很好的选择。

它主要应用于客户端开发,尤其是状态管理较为简单的客户端应用时,推荐使用MVP。

自从人们开始开发软件用户界面以来,就出现了一些流行的设计模式来简化这一工作。

例如,MVP(Model-View-Presenter)模式在各种UI编程平台中广受欢迎。

MVP是几十年来使用的Model-View-Controller模式的变体。对于不熟悉MVP模式的读者,简单来说:屏幕上看到的是View,显示的数据是Model,而将两者连接起来的是Presenter。View通过Model数据填充界面,响应用户输入,并提供输入验证(例如委托给Model),同时使用Presenter来处理这些任务。

——MSDN杂志第09期


(John Gossman)

(笔者个人是WPF等C#技术的忠实用户,因此作为程序员,我一直希望能有机会得到John Gossman的签名。他可以说是许多微软技术粉丝的偶像。)

MVVM(Model-View-ViewModel)

2005年,John Gossman在他的博客中首次撰写了关于MVVM的文章。

MVP模式在分离View和逻辑、提升测试便利性以及控制依赖性方面无疑是非常有效的模式。

然而,由于结构性、生产力以及绑定方面的局限性,MVVM逐渐成为自然的选择。

在MVP中,View和Presenter之间的手动连接会产生大量的模板代码,并且在View与Presenter之间传递数据时需要编写大量重复的过程。

此外,由于MVP缺乏数据绑定机制,频繁调用Presenter会导致较大的开销。而在MVVM中,ViewModel负责状态绑定并实现自动更新,即具备“响应式”特性。

最终,现代UI框架大多转向了MVVM架构,例如MAUI、WPF等基于数据绑定的UI框架,MVVM成为了最理想的选择。

MVVM的构成要素如下:

构成要素

角色

说明

Model

数据状态管理

负责应用程序的核心数据和业务逻辑,包括服务器API、数据库、业务规则等。它直接与ViewModel交换数据。

View

UI显示及绑定

以视觉方式呈现用户界面(UI)。View通过绑定到ViewModel,在状态发生变化时自动更新。自身没有复杂的逻辑,通常以声明式方式构建。

ViewModel

状态保持及中介

接收来自View的用户输入,与Model交互以更新状态,并以可绑定的形式将状态提供给View。ViewModel与View分离,便于进行单元测试。

实际上,在大多数使用C#的开发环境中,如WPF、Xamarin、MAUI等,MVVM被采用为默认架构。

这不仅仅是惯例问题,而是因为这些框架本身假定了基于XAML和数据绑定的UI声明方式。

因此,在C#生态系统中,考虑到UI与逻辑的分离、状态同步的自动化以及测试便利性,MVVM几乎成为了“默认选项”。

开发者也自然而然地熟悉了ViewModel这一中间层,MVVM也因此成为了C#开发者中最广为人知的UI模式之一。

那么,什么时候应该使用MVVM?

  1. 使用XAML基础的UI时特别有效:
    在WPF、MAUI、Xamarin.Forms等框架中,基于数据绑定的UI结构很自然地会导向MVVM。

  2. 状态频繁变化且需要实时反映到UI时:
    例如表单状态、过滤条件、网络响应结果、有效性验证等场景中推荐使用。

  3. 希望通过可测试的ViewModel层验证业务逻辑时:
    ViewModel与View分离,使其易于进行单元测试。

  4. 设计师与开发者需要分工协作时:
    View(XAML)与ViewModel(C#)明确分离,从而提高了协作效率。

当然,MVVM并不总是最佳选择。当界面非常简单且状态仅有一两个时,反而使用代码后置或MVP结构可能会更加简洁。

虽然MVVM无疑是一个非常优秀的模式,但在开发简单的工具类应用时,它可能会引入不必要的复杂性,可以说是一把双刃剑。

 

(André Staltz)

MVI(Model-View-Intent)

2015年,André Staltz在JSConf Budapest上发布了Cycle.js框架。

MVI是一种以单向状态流为核心的现代UI架构,而Cycle.js则是将MVI概念提升到结构化层面的代表性框架。Cycle.js通过基于流(Stream)的方式完全抽象了Intent → Model → View → Intent的循环结构,并将副作用(Side Effect)分离到Driver中,尝试实现函数式UI的实际应用。

MVI不仅仅是一个简单的模式,更是改变了编程模型的历史性演变路径的一部分。

MVI的构成要素如下:

构成要素

角色

说明

Model (State)

UI的单一状态

表示当前View状态的单一不可变对象。所有UI状态都包含在这个State中,且状态始终保持完整形式。

View

基于状态渲染界面

根据状态(State)绘制UI,并将用户的交互转换为Intent传递出去。View是无状态的,以声明式方式运行。

Intent

定义用户事件

将用户的输入(如点击、输入等)语义化表达的对象。例如:AddTodoClicked、SearchTextChanged、RetryButtonPressed。

Reducer

状态转移函数

接收前一个状态和Intent,返回新的状态。这是一个纯函数,不包含副作用,仅计算状态。

Effect/Side Effect(可选)

外部系统调用等非确定性操作

如API调用、文件I/O、数据库访问等非纯操作被单独分离处理。在Redux或Elm中通常以Eff

虽然看起来复杂,但实际上可以更简单地理解:

Intent → Reducer → State → View → Intent 结构

通过单向状态流转,路径变得可预测,从而使得调试更加容易。

Swift、Kotlin StateFlow、Jetpack Compose等技术也很有名,但最重要的是,Flutter中经常使用这种技术。

MVI是继MVVM之后出现的一种基于单向状态流的声明式UI架构。

它通过单一不可变对象管理所有UI状态,并基于用户的意图(Intent)执行清晰的状态转移。

强调测试能力、状态可预测性和副作用分离的结构,已成为现代声明式UI框架中的事实标准。

那么,什么时候应该使用MVI?

  1. 当UI状态复杂且难以追踪时:
    通过将状态定义为单一对象,并通过意图(Intent)控制状态转移,使调试变得更加容易。

  2. 分离副作用以便更容易区分非确定性操作:
    通过分离副作用,调试变得更加直观。

然而,这些优点也表明,如果UI简单或未基于函数式编程(FP)进行思考,则可能会显得复杂。

也就是说,由于其自身具有一定的复杂性以及较高的学习曲线,随意引入该架构可能会面临困难。

“简单的事情应该简单,复杂的事情应该可能。” — Alan Kay

结语

架构就像一门没有语法的语言。

我们选择架构并不是因为它是我们的主观决定,而是所使用的技术、试图解决的问题以及所属组织的哲学对我们提出了结构上的要求。

从MVC、MVP、MVVM、MVI,到最近的VIPER、RIBs等,无数种架构存在。

所有这些架构都不过是对“如何更好地将人类的意图反映到代码中”这一古老问题的回答罢了。

本文旨在展示架构不仅仅是简单地应用,而是随着需求的变化不断演化的。

编程并不仅仅是记忆答案并复制粘贴的人的工作。

编程是一种思维方式,永远追求更好的答案。

架构不是理论,而仅仅是一个解决现场问题的工具。

即使今天选择了一种架构,明天又改变了自己的决定,只要这个判断是基于上下文考虑的,那这就是一个好的选择。

希望这篇文章能在你做出判断时提供一些帮助。

相关文章:

  • 楼宇【复习】
  • python打卡day29@浙大疏锦行
  • AGI大模型(23):LangChain框架快速入门之LangChain介绍
  • unity开发游戏实现角色筛选预览
  • 2025年PMP 学习十九 第12章 项目采购管理
  • 数据结构:二叉树一文详解
  • CSS-in-JS:现代前端样式管理的革新
  • 【MySQL】(12) 事务
  • 功分器简介
  • GORM 知识点入门
  • 机器学习09-正规方程
  • MetaMask安装及使用-使用水龙头获取测试币的坑?
  • 计算机网络 - 2.基础协议
  • 什么是 Boosting
  • 2025 ISCC 练武赛Pwn-wp(含附件)
  • KAG:通过知识增强生成提升专业领域的大型语言模型(五)
  • ABP vNext 多租户系统实现登录页自定义 Logo 的最佳实践
  • 【Canvas与诗词】醉里挑灯看剑 梦回吹角连营
  • TYUT-企业级开发教程-第三章
  • Qt Widgets模块功能详细说明,基本控件:QPushButton(二)
  • 国家统计局:2024年城镇单位就业人员工资平稳增长
  • 媒体:“重病老人银行取款身亡”涉事家属称已和解,银行将支付十万
  • 马上评|清理“滥竽充数者”,为医者正名
  • 哪种“网红减肥法”比较靠谱?医学专家和运动专家共同解答
  • 奥迪车加油时频繁“跳枪”维修两年未解决,4S店拒退换:可延长质保
  • 佩斯科夫:若普京认为必要,将公布土耳其谈判俄方代表人选