应用程序架构指南
以下页面演示了如何使用最佳实践构建应用程序。本指南中的建议适用于大多数应用程序,使它们更容易扩展、测试和维护。但是,这些只是指南,而不是一成不变的规则,您应该根据您的独特需求调整它们。
本节提供了 Flutter 应用程序架构的高级概述。它解释了应用程序的各个层,以及每一层中存在的类。下一节提供了具体的代码示例,并逐步讲解了一个实现了这些建议的 Flutter 应用程序。
项目结构概述
#在设计 Flutter 应用程序时,最重要的原则是遵循关注点分离。您的 Flutter 应用程序应该分为两个主要层:UI 层和数据层。
每一层都进一步细分为不同的组件,每个组件都具有不同的职责、明确定义的接口、边界和依赖关系。本指南建议您将应用程序划分为以下组件:
- 视图 (Views)
- 视图模型 (View models)
- 资源库 (Repositories)
- 服务 (Services)
MVVM
#如果您遇到过模型-视图-视图模型设计模式 (MVVM),那么您会对此很熟悉。MVVM 是一种设计模式,它将应用程序的一个特性分成三个部分:模型 (Model
)、视图模型 (ViewModel
) 和视图 (View
)。视图和视图模型构成应用程序的 UI 层。资源库和服务代表应用程序的数据,或 MVVM 的模型层。下一节将定义每个组件。
![MVVM design pattern](/assets/images/docs/app-architecture/guide/mvvm-intro-with-layers.png)
应用程序中的每个特性都将包含一个视图来描述 UI 和一个视图模型来处理逻辑,一个或多个资源库作为应用程序数据的真实来源,以及零个或多个与外部 API(如客户端服务器和平台插件)交互的服务。
应用程序的单个特性可能需要所有以下对象:
![An example of the Dart objects that might exist in one feature using the architecture described on page.](/assets/images/docs/app-architecture/guide/feature-architecture-example.png)
在本页末尾将对这些对象以及连接它们的箭头进行详细解释。在本指南中,将使用该图的以下简化版本作为锚点。
![A simplified diagram of the architecture described on this page.](/assets/images/docs/app-architecture/guide/feature-architecture-simplified.png)
UI 层
#应用程序的 UI 层负责与用户交互。它将应用程序的数据显示给用户并接收用户输入,例如点击事件和表单输入。
UI 对数据更改或用户输入做出反应。当 UI 从资源库接收新数据时,它应该重新渲染以显示新数据。当用户与 UI 交互时,它应该更改以反映该交互。
基于 MVVM 设计模式,UI 层由两个架构组件组成:
- 视图描述如何将应用程序数据呈现给用户。具体来说,它指的是构成一个特性的 小部件组合 。例如,视图通常(但并非总是)是一个具有
Scaffold
小部件以及小部件树中所有位于其下的小部件的屏幕。视图还负责响应用户交互并将事件传递给视图模型。 - 视图模型包含将应用程序数据转换为UI 状态的逻辑,因为来自资源库的数据格式通常与需要显示的数据格式不同。例如,您可能需要组合来自多个资源库的数据,或者您可能想要过滤数据记录列表。
视图和视图模型应该具有 1:1 的关系。
![A simplified diagram of the architecture described on this page with the view and view model objects highlighted.](/assets/images/docs/app-architecture/guide/feature-architecture-simplified-UI-highlighted.png)
简而言之,视图模型管理 UI 状态,而视图显示该状态。使用视图和视图模型,您的 UI 层可以在配置更改(例如屏幕旋转)期间保持状态,并且您可以独立于 Flutter 小部件测试 UI 的逻辑。
应用程序的一个特性以用户为中心,因此由 UI 层定义。每对视图和视图模型的每个实例都在您的应用程序中定义一个特性。这通常是您应用程序中的一个屏幕,但它不必是屏幕。例如,考虑登录和注销。登录通常在一个特定屏幕上完成,该屏幕的唯一目的是为用户提供登录方式。在应用程序代码中,登录屏幕将由 LoginViewModel
类和 LoginView
类组成。
另一方面,注销应用程序通常不会在专用屏幕上完成。注销功能通常作为菜单、用户帐户屏幕或许多不同位置中的按钮呈现给用户。它通常在多个位置呈现。在这种情况下,您可能有一个 LogoutViewModel
和一个 LogoutView
,它只包含一个可以放入其他小部件的按钮。
视图
#在 Flutter 中,视图是应用程序的小部件类。视图是渲染 UI 的主要方法,不应包含任何业务逻辑。它们应该从视图模型传递所有需要渲染的数据。
![A simplified diagram of the architecture described on this page with the view object highlighted.](/assets/images/docs/app-architecture/guide/feature-architecture-simplified-View-highlighted.png)
视图应该只包含以下逻辑:
- 基于视图模型中的标志或可空字段显示和隐藏小部件的简单 if 语句
- 动画逻辑
- 基于设备信息(如屏幕尺寸或方向)的布局逻辑
- 简单路由逻辑
所有与数据相关的逻辑都应在视图模型中处理。
视图模型
#视图模型公开渲染视图所需的应用程序数据。在本页上描述的架构设计中,Flutter 应用程序中的大部分逻辑都位于视图模型中。
![A simplified diagram of the architecture described on this page with the view model object highlighted.](/assets/images/docs/app-architecture/guide/feature-architecture-simplified-ViewModel-highlighted.png)
视图模型的主要职责包括:
- 从资源库检索应用程序数据,并将其转换为适合在视图中呈现的格式。例如,它可能会过滤、排序或聚合数据。
- 维持视图中所需的当前状态,以便视图可以在不丢失数据的情况下重建。例如,它可能包含布尔标志以有条件地渲染视图中的小部件,或者包含跟踪屏幕上活动轮播部分的字段。
- 向视图公开回调(称为 命令 ),这些回调可以附加到事件处理程序,例如按钮按下或表单提交。
命令以命令模式命名,是 Dart 函数,允许视图执行复杂的逻辑而无需了解其实现。命令作为视图模型类的成员编写,由视图类中的手势处理程序调用。
您可以在应用程序架构案例研究的UI 层部分找到视图、视图模型和命令的示例。
有关 Flutter 中 MVVM 的简要介绍,请查看状态管理基础知识。
数据层
#应用程序的数据层处理您的业务数据和逻辑。两部分架构构成了数据层:服务和资源库。这些部分应该具有明确定义的输入和输出,以简化其可重用性和可测试性。
![A simplified diagram of the architecture described on this page with the Data layer highlighted.](/assets/images/docs/app-architecture/guide/feature-architecture-simplified-Data-highlighted.png)
使用 MVVM 语言,服务和资源库构成您的 模型层 。
资源库
#资源库类是模型数据的真实来源。它们负责从服务轮询数据,并将原始数据转换为 域模型 。域模型表示应用程序所需的数据,以视图模型类可以使用的格式进行格式化。对于应用程序中处理的每种不同类型的数据,都应该有一个资源库类。
资源库处理与服务相关的业务逻辑,例如:
- 缓存
- 错误处理
- 重试逻辑
- 刷新数据
- 轮询服务以获取新数据
- 基于用户操作刷新数据
![A simplified diagram of the architecture described on this page with the Repository object highlighted.](/assets/images/docs/app-architecture/guide/feature-architecture-simplified-Repository-highlighted.png)
资源库将应用程序数据输出为域模型。例如,社交媒体应用程序可能有一个 UserProfileRepository
类,该类公开一个 Stream<UserProfile?>
,每当用户登录或注销时,它都会发出一个新值。
资源库输出的模型由视图模型使用。资源库和视图模型之间存在多对多的关系。视图模型可以使用许多资源库来获取它需要的数据,而一个资源库可以被许多视图模型使用。
资源库不应彼此了解。如果您的应用程序具有需要来自两个资源库的数据的业务逻辑,则应在视图模型或域层中组合数据,尤其是在您的资源库到视图模型的关系很复杂的情况下。
服务
#服务位于应用程序的最低层。它们包装 API 端点并公开异步响应对象,例如 Future
和 Stream
对象。它们仅用于隔离数据加载,并且不保存任何状态。您的应用程序应该每个数据源有一个服务类。服务可能包装的端点示例包括:
- 底层平台,例如 iOS 和 Android API
- REST 端点
- 本地文件
根据经验,当必要的数据位于应用程序的 Dart 代码之外时,服务最有用——这对于上述每个示例都是正确的。
服务和资源库之间存在多对多的关系。单个资源库可以使用多个服务,而一个服务可以被多个资源库使用。
![A simplified diagram of the architecture described on this page with the Service object highlighted.](/assets/images/docs/app-architecture/guide/feature-architecture-simplified-Service-highlighted.png)
可选:域层
#随着应用程序的增长和功能的增加,您可能需要抽象出以下逻辑:
向您的视图模型添加过多的复杂性。这些类通常称为交互器或 用例 。
用例负责使 UI 和数据层之间的交互更简单、更易于重用。它们从资源库获取数据,并使其适合 UI 层。
![MVVM design pattern with an added domain layer object](/assets/images/docs/app-architecture/guide/mvvm-intro-with-domain-layer.png)
用例主要用于封装原本位于视图模型中的业务逻辑,并且满足以下一个或多个条件:
- 需要合并来自多个资源库的数据
- 极其复杂
- 该逻辑将被不同的视图模型重用
此层是可选的,因为并非所有应用程序或应用程序中的所有功能都需要这些要求。如果您怀疑您的应用程序会受益于此额外层,请考虑其优缺点:
优点 | 缺点 |
---|---|
✅避免视图模型中的代码重复 | ❌增加架构的复杂性,增加更多类和更高的认知负荷 |
✅通过将复杂的业务逻辑与 UI 逻辑分离来提高可测试性 | ❌测试需要额外的模拟 |
✅提高视图模型的代码可读性 | ❌向您的代码添加额外的样板 |
使用用例进行数据访问
#添加域层时,另一个需要考虑的问题是视图模型是否将继续直接访问资源库数据,或者您是否会强制视图模型通过用例来获取其数据。换句话说,您是否会在需要时添加用例?也许当您注意到视图模型中重复的逻辑时?或者,每次视图模型需要数据时,您是否都会创建一个用例,即使用例中的逻辑很简单?
如果您选择后者,它会加剧前面概述的优缺点。您的应用程序代码将非常模块化且可测试,但它也会增加大量不必要的开销。
一个好的方法是仅在需要时添加用例。如果您发现您的视图模型大多数时间都是通过用例访问数据的,您可以随时重构您的代码以专门使用用例。本指南后面使用的示例应用程序对某些功能使用用例,但也有一些视图模型直接与资源库交互。一个复杂的功能最终可能看起来像这样:
这种添加用例的方法由以下规则定义:
- 用例依赖于资源库
- 用例和资源库之间存在多对多的关系
- 视图模型依赖于一个或多个用例 和 一个或多个资源库
这种使用用例的方法最终看起来不像分层的千层面,而更像是一道配有两道主菜(UI 和数据层)和一道配菜(域层)的拼盘。用例只是具有明确定义的输入和输出的实用程序类。这种方法灵活且可扩展,但需要更大的努力来保持秩序。
反馈
#由于本网站的这一部分正在不断发展,我们欢迎您的反馈!
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。