跳至主要内容

应用架构指南

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

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

项目结构概述

#

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

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

  • 视图
  • 视图模型
  • 存储库
  • 服务

MVVM

#

如果您遇到过模型-视图-视图模型设计模式 (MVVM),您会发现它很熟悉。MVVM 是一种设计模式,它将应用的一个功能分成三个部分:ModelViewModelView。视图和视图模型构成应用的 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 交互时,它应更改以反映该交互。

UI 层由两个架构组件组成,基于 MVVM 设计模式

  • 视图描述如何向用户呈现应用数据。具体来说,它指的是构成一个功能的*小部件组合*。例如,视图通常(但并非总是)是一个具有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 层和数据层)和一道配菜(领域层)的晚餐。用例只是具有明确定义的输入和输出的实用程序类。这种方法灵活且可扩展,但需要更大的努力来保持秩序。

反馈

#

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