Skip to main content

应用程序架构指南

以下页面演示了如何使用最佳实践构建应用程序。本指南中的建议适用于大多数应用程序,使它们更容易扩展、测试和维护。但是,这些只是指南,而不是一成不变的规则,您应该根据您的独特需求调整它们。

本节提供了 Flutter 应用程序架构的高级概述。它解释了应用程序的各个层,以及每一层中存在的类。下一节提供了具体的代码示例,并逐步讲解了一个实现了这些建议的 Flutter 应用程序。

项目结构概述

#

在设计 Flutter 应用程序时,最重要的原则是遵循关注点分离。您的 Flutter 应用程序应该分为两个主要层:UI 层和数据层。

每一层都进一步细分为不同的组件,每个组件都具有不同的职责、明确定义的接口、边界和依赖关系。本指南建议您将应用程序划分为以下组件:

  • 视图 (Views)
  • 视图模型 (View models)
  • 资源库 (Repositories)
  • 服务 (Services)

MVVM

#

如果您遇到过模型-视图-视图模型设计模式 (MVVM),那么您会对此很熟悉。MVVM 是一种设计模式,它将应用程序的一个特性分成三个部分:模型 (Model)、视图模型 (ViewModel) 和视图 (View)。视图和视图模型构成应用程序的 UI 层。资源库和服务代表应用程序的数据,或 MVVM 的模型层。下一节将定义每个组件。

MVVM design pattern

应用程序中的每个特性都将包含一个视图来描述 UI 和一个视图模型来处理逻辑,一个或多个资源库作为应用程序数据的真实来源,以及零个或多个与外部 API(如客户端服务器和平台插件)交互的服务。

应用程序的单个特性可能需要所有以下对象:

An example of the Dart objects that might exist in one feature using the architecture described on page.

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

A simplified diagram of the architecture described on this page.

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.

简而言之,视图模型管理 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.

视图应该只包含以下逻辑:

  • 基于视图模型中的标志或可空字段显示和隐藏小部件的简单 if 语句
  • 动画逻辑
  • 基于设备信息(如屏幕尺寸或方向)的布局逻辑
  • 简单路由逻辑

所有与数据相关的逻辑都应在视图模型中处理。

视图模型

#

视图模型公开渲染视图所需的应用程序数据。在本页上描述的架构设计中,Flutter 应用程序中的大部分逻辑都位于视图模型中。

A simplified diagram of the architecture described on this page with the view model object highlighted.

视图模型的主要职责包括:

  • 从资源库检索应用程序数据,并将其转换为适合在视图中呈现的格式。例如,它可能会过滤、排序或聚合数据。
  • 维持视图中所需的当前状态,以便视图可以在不丢失数据的情况下重建。例如,它可能包含布尔标志以有条件地渲染视图中的小部件,或者包含跟踪屏幕上活动轮播部分的字段。
  • 向视图公开回调(称为 命令 ),这些回调可以附加到事件处理程序,例如按钮按下或表单提交。

命令以命令模式命名,是 Dart 函数,允许视图执行复杂的逻辑而无需了解其实现。命令作为视图模型类的成员编写,由视图类中的手势处理程序调用。

您可以在应用程序架构案例研究UI 层部分找到视图、视图模型和命令的示例。

有关 Flutter 中 MVVM 的简要介绍,请查看状态管理基础知识

数据层

#

应用程序的数据层处理您的业务数据和逻辑。两部分架构构成了数据层:服务和资源库。这些部分应该具有明确定义的输入和输出,以简化其可重用性和可测试性。

A simplified diagram of the architecture described on this page with the Data layer highlighted.

使用 MVVM 语言,服务和资源库构成您的 模型层

资源库

#

资源库类是模型数据的真实来源。它们负责从服务轮询数据,并将原始数据转换为 域模型 。域模型表示应用程序所需的数据,以视图模型类可以使用的格式进行格式化。对于应用程序中处理的每种不同类型的数据,都应该有一个资源库类。

资源库处理与服务相关的业务逻辑,例如:

  • 缓存
  • 错误处理
  • 重试逻辑
  • 刷新数据
  • 轮询服务以获取新数据
  • 基于用户操作刷新数据
A simplified diagram of the architecture described on this page with the Repository object highlighted.

资源库将应用程序数据输出为域模型。例如,社交媒体应用程序可能有一个 UserProfileRepository 类,该类公开一个 Stream<UserProfile?>,每当用户登录或注销时,它都会发出一个新值。

资源库输出的模型由视图模型使用。资源库和视图模型之间存在多对多的关系。视图模型可以使用许多资源库来获取它需要的数据,而一个资源库可以被许多视图模型使用。

资源库不应彼此了解。如果您的应用程序具有需要来自两个资源库的数据的业务逻辑,则应在视图模型或域层中组合数据,尤其是在您的资源库到视图模型的关系很复杂的情况下。

服务

#

服务位于应用程序的最低层。它们包装 API 端点并公开异步响应对象,例如 FutureStream 对象。它们仅用于隔离数据加载,并且不保存任何状态。您的应用程序应该每个数据源有一个服务类。服务可能包装的端点示例包括:

  • 底层平台,例如 iOS 和 Android API
  • REST 端点
  • 本地文件

根据经验,当必要的数据位于应用程序的 Dart 代码之外时,服务最有用——这对于上述每个示例都是正确的。

服务和资源库之间存在多对多的关系。单个资源库可以使用多个服务,而一个服务可以被多个资源库使用。

A simplified diagram of the architecture described on this page with the Service object highlighted.

可选:域层

#

随着应用程序的增长和功能的增加,您可能需要抽象出以下逻辑:

向您的视图模型添加过多的复杂性。这些类通常称为交互器或 用例

用例负责使 UI 和数据层之间的交互更简单、更易于重用。它们从资源库获取数据,并使其适合 UI 层。

MVVM design pattern with an added domain layer object

用例主要用于封装原本位于视图模型中的业务逻辑,并且满足以下一个或多个条件:

  1. 需要合并来自多个资源库的数据
  2. 极其复杂
  3. 该逻辑将被不同的视图模型重用

此层是可选的,因为并非所有应用程序或应用程序中的所有功能都需要这些要求。如果您怀疑您的应用程序会受益于此额外层,请考虑其优缺点:

优点缺点
✅避免视图模型中的代码重复❌增加架构的复杂性,增加更多类和更高的认知负荷
✅通过将复杂的业务逻辑与 UI 逻辑分离来提高可测试性❌测试需要额外的模拟
✅提高视图模型的代码可读性❌向您的代码添加额外的样板

使用用例进行数据访问

#

添加域层时,另一个需要考虑的问题是视图模型是否将继续直接访问资源库数据,或者您是否会强制视图模型通过用例来获取其数据。换句话说,您是否会在需要时添加用例?也许当您注意到视图模型中重复的逻辑时?或者,每次视图模型需要数据时,您是否都会创建一个用例,即使用例中的逻辑很简单?

如果您选择后者,它会加剧前面概述的优缺点。您的应用程序代码将非常模块化且可测试,但它也会增加大量不必要的开销。

一个好的方法是仅在需要时添加用例。如果您发现您的视图模型大多数时间都是通过用例访问数据的,您可以随时重构您的代码以专门使用用例。本指南后面使用的示例应用程序对某些功能使用用例,但也有一些视图模型直接与资源库交互。一个复杂的功能最终可能看起来像这样:

A simplified diagram of the architecture described on this page with a use case object.

这种添加用例的方法由以下规则定义:

  • 用例依赖于资源库
  • 用例和资源库之间存在多对多的关系
  • 视图模型依赖于一个或多个用例 一个或多个资源库

这种使用用例的方法最终看起来不像分层的千层面,而更像是一道配有两道主菜(UI 和数据层)和一道配菜(域层)的拼盘。用例只是具有明确定义的输入和输出的实用程序类。这种方法灵活且可扩展,但需要更大的努力来保持秩序。

反馈

#

由于本网站的这一部分正在不断发展,我们欢迎您的反馈