应用架构指南
以下页面演示了如何使用最佳实践构建应用。本指南中的建议适用于大多数应用,可以使其更易于扩展、测试和维护。然而,这些是指导原则,并非固定不变的规则,您应根据自己的独特需求进行调整。
本节概述了 Flutter 应用如何进行架构设计。它解释了应用的各个层级以及每个层级中存在的类。本节之后的部分将提供具体的代码示例,并详细讲解一个实现了这些建议的 Flutter 应用。
项目结构概述
#关注点分离是设计 Flutter 应用时最重要的原则。您的 Flutter 应用应分为两个主要层级:UI 层和数据层。
每个层级都进一步细分为不同的组件,每个组件都有独特的职责、定义良好的接口、边界和依赖关系。本指南建议您将应用拆分为以下组件:
- 视图
- 视图模型
- 存储库
- 服务
MVVM
#如果您遇到过模型-视图-视图模型 (Model-View-ViewModel) 架构模式 (MVVM),对此会很熟悉。MVVM 是一种架构模式,将应用的一个功能分为三个部分:模型 (Model)
、视图模型 (ViewModel)
和 视图 (View)
。视图和视图模型构成了应用的 UI 层。存储库和服务代表应用的数据,或 MVVM 的模型层。这些组件中的每一个都将在下一节中定义。

应用中的每个功能都将包含一个描述 UI 的视图和一个处理逻辑的视图模型,一个或多个作为应用数据真实来源的存储库,以及零个或多个与外部 API(如客户端服务器和平台插件)交互的服务。
应用的单个功能可能需要以下所有对象:

到本页末尾,这些对象以及连接它们的箭头都将得到详细解释。在本指南中,以下该图的简化版本将用作锚点。

UI 层
#应用的 UI 层负责与用户交互。它向用户显示应用的数据,并接收用户输入,例如点击事件和表单输入。
UI 会对数据变化或用户输入做出反应。当 UI 从存储库接收到新数据时,它应该重新渲染以显示新数据。当用户与 UI 交互时,UI 应该随之改变以反映该交互。
UI 层由两个基于 MVVM 设计模式的架构组件组成:
- 视图描述了如何向用户呈现应用数据。具体来说,它们指的是小部件组合,用于构成一个功能。例如,视图通常(但并非总是)是具有
Scaffold
小部件以及小部件树中其下方所有小部件的屏幕。视图还负责响应用户交互将事件传递给视图模型。 - 视图模型包含将应用数据转换为UI 状态的逻辑,因为来自存储库的数据通常与需要显示的数据格式不同。例如,您可能需要组合来自多个存储库的数据,或者您可能想要过滤数据记录列表。
视图和视图模型应具有一对一的关系。

最简单地说,视图模型管理 UI 状态,视图显示该状态。使用视图和视图模型,您的 UI 层可以在配置更改(例如屏幕旋转)期间保持状态,并且您可以独立于 Flutter 小部件测试 UI 的逻辑。
应用的一个功能是以用户为中心的,因此由 UI 层定义。每对视图和视图模型的实例都定义了应用中的一个功能。这通常是应用中的一个屏幕,但并非必须如此。例如,考虑登录和注销。登录通常在特定屏幕上完成,其唯一目的是为用户提供登录方式。在应用代码中,登录屏幕将由 LoginViewModel
类和 LoginView
类组成。
另一方面,注销应用通常不在专用屏幕上完成。注销功能通常以菜单中的按钮、用户帐户屏幕或任何数量的不同位置呈现给用户。它通常在多个位置呈现。在这种情况下,您可能有一个 LogoutViewModel
和一个 LogoutView
,其中只包含一个可以放入其他小部件的按钮。
视图
#在 Flutter 中,视图是应用的小部件类。视图是渲染 UI 的主要方法,不应包含任何业务逻辑。它们应从视图模型接收渲染所需的所有数据。

视图应包含的唯一逻辑是:
- 基于视图模型中的标志或可空字段来显示和隐藏小部件的简单 if 语句
- 动画逻辑
- 基于设备信息(如屏幕尺寸或方向)的布局逻辑。
- 简单路由逻辑
所有与数据相关的逻辑都应在视图模型中处理。
视图模型
#视图模型公开渲染视图所需的应用数据。在本页描述的架构设计中,Flutter 应用的大部分逻辑都存在于视图模型中。

视图模型的主要职责包括:
- 从存储库中检索应用数据,并将其转换为适合在视图中呈现的格式。例如,它可能会过滤、排序或聚合数据。
- 维护视图中所需的当前状态,以便视图可以在不丢失数据的情况下重建。例如,它可能包含布尔标志以在视图中条件性地渲染小部件,或者包含一个字段来跟踪轮播图的哪个部分在屏幕上处于活动状态。
- 向视图公开可以附加到事件处理程序(如按钮按下或表单提交)的回调(称为命令)。
命令以命令模式命名,是 Dart 函数,允许视图在不知道其实现的情况下执行复杂逻辑。命令作为视图模型类的成员编写,由视图类中的手势处理程序调用。
您可以在应用架构案例研究的UI 层部分找到视图、视图模型和命令的示例。
要温和地了解 Flutter 中的 MVVM,请查看状态管理基础。
数据层
#应用的数据层处理您的业务数据和逻辑。数据层由两个架构部分组成:服务和存储库。这些部分应具有明确的输入和输出,以简化其可重用性和可测试性。

使用 MVVM 术语,服务和存储库构成了您的模型层。
存储库
#存储库 (Repository) 类是模型数据的真实来源。它们负责从服务中轮询数据,并将原始数据转换为领域模型。领域模型代表应用所需的数据,并以视图模型类可以使用的格式进行格式化。您的应用中处理的每种不同类型的数据都应该有一个存储库类。
存储库处理与服务相关的业务逻辑,例如:
- 缓存
- 错误处理
- 重试逻辑
- 刷新数据
- 轮询服务以获取新数据
- 根据用户操作刷新数据

存储库将应用数据作为领域模型输出。例如,社交媒体应用可能有一个 UserProfileRepository
类,它公开一个 Stream<UserProfile?>
,每当用户登录或注销时都会发出新值。
存储库输出的模型由视图模型使用。存储库和视图模型之间存在多对多的关系。一个视图模型可以使用多个存储库来获取所需数据,一个存储库也可以被多个视图模型使用。
存储库之间不应相互感知。如果您的应用有需要来自两个存储库的数据的业务逻辑,您应该在视图模型或领域层中组合数据,尤其是在存储库到视图模型的关系复杂时。
服务
#服务位于应用的最底层。它们封装 API 端点并公开异步响应对象,例如 Future
和 Stream
对象。它们仅用于隔离数据加载,并且不持有任何状态。您的应用每个数据源都应该有一个服务类。服务可能封装的端点示例包括:
- 底层平台,如 iOS 和 Android API
- REST 端点
- 本地文件
根据经验,当所需数据存在于应用 Dart 代码之外时(以上所有示例都属于这种情况),服务是最有用的。
服务和存储库之间存在多对多的关系。一个存储库可以使用多个服务,一个服务也可以被多个存储库使用。

可选:领域层
#随着应用的增长和功能的增加,您可能需要抽象出那些给视图模型增加过多复杂度的逻辑。这些类通常被称为交互器或用例。
用例负责使 UI 层和数据层之间的交互更简单、更可重用。它们从存储库中获取数据并使其适用于 UI 层。

用例主要用于封装否则将存在于视图模型中的业务逻辑,并满足以下一个或多个条件:
- 需要合并来自多个存储库的数据
- 非常复杂
- 该逻辑将被不同的视图模型重用
此层是可选的,因为并非所有应用或应用中的功能都有这些要求。如果您认为您的应用会从此附加层中受益,请考虑其优缺点:
优点 | 缺点 |
---|---|
✅ 避免视图模型中的代码重复 | ❌ 增加架构的复杂性,引入更多类和更高的认知负荷 |
✅ 通过将复杂的业务逻辑与 UI 逻辑分离来提高可测试性 | ❌ 测试需要额外的模拟对象 |
✅ 提高视图模型中的代码可读性 | ❌ 增加了代码中的额外样板 |
使用用例进行数据访问
#添加领域层时的另一个考虑因素是,视图模型是否会继续直接访问存储库数据,或者您是否会强制视图模型通过用例来获取数据。换句话说,您会根据需要添加用例吗?也许当您注意到视图模型中重复的逻辑时?或者,无论用例中的逻辑是否简单,每次视图模型需要数据时都会创建一个用例吗?
如果您选择后者,它会加剧前面概述的优缺点。您的应用代码将极具模块化和可测试性,但也会增加大量不必要的开销。
一个好的方法是仅在需要时添加用例。如果您发现您的视图模型大部分时间都是通过用例访问数据,那么您随时可以重构代码以完全利用用例。本指南后面使用的示例应用为某些功能提供了用例,但也包含直接与存储库交互的视图模型。一个复杂的功能最终可能看起来像这样:
添加用例的这种方法由以下规则定义:
- 用例依赖于存储库
- 用例和存储库之间存在多对多的关系
- 视图模型依赖于一个或多个用例以及一个或多个存储库
这种使用用例的方法最终看起来不像分层千层面,而更像是一份摆盘正餐,有两道主菜(UI 和数据层)和一道配菜(领域层)。用例只是具有明确输入和输出的实用工具类。这种方法灵活且可扩展,但需要更勤奋地维护秩序。
反馈
#由于本网站的此部分正在不断发展,我们欢迎您的反馈!