接下來的頁面將展示如何使用最佳實踐來構建一個應用。本指南中的建議可以應用於大多數應用,使它們更容易擴充套件、測試和維護。然而,它們是指導原則,而不是嚴格的規則,您應該根據自己的獨特需求進行調整。

本節提供了一個 Flutter 應用如何進行架構的高層概覽。它解釋了應用的分層以及每層中存在的類。下一節將提供具體的程式碼示例,並逐步介紹一個實現了這些建議的 Flutter 應用。

專案結構概覽

#

關注點分離 是設計 Flutter 應用時最重要的原則。您的 Flutter 應用應該分為兩個大的層面:UI 層和資料層。

每一層又進一步劃分為不同的元件,每個元件都有明確的職責、定義良好的介面、邊界和依賴關係。本指南建議您將應用劃分為以下元件:

  • 檢視
  • ViewModel
  • Repository
  • Service

MVVM

#

如果您遇到過 Model-View-ViewModel (MVVM) 架構模式,那麼這會很熟悉。MVVM 是一種架構模式,它將應用程式的一個功能分為三個部分:ModelViewModelView。View 和 ViewModel 構成了應用程式的 UI 層。Repository 和 Service 代表應用程式的資料,或者 MVVM 中的 Model 層。這些元件中的每一個都在下一節中定義。

MVVM architectural pattern

應用程式中的每個功能都將包含一個用於描述 UI 的 View,一個用於處理邏輯的 ViewModel,一個或多個 Repository 作為應用程式資料的真相來源,以及零個或多個與外部 API(如客戶端伺服器和平臺外掛)互動的 Service。

應用程式的單個功能可能需要以下所有物件:

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 從 Repository 接收到新資料時,它應該重新渲染以顯示新資料。當用戶與 UI 互動時,它應該發生變化以反映該互動。

UI 層由兩個架構元件組成,基於 MVVM 設計模式:

  • View 描述瞭如何將應用程式資料呈現給使用者。具體來說,它們指的是構成一個功能的Widget 組合。例如,一個 View 通常(但不總是)是一個螢幕,它有一個 Scaffold Widget,以及 Widget 樹中它下面的所有 Widget。View 還負責響應使用者互動,將事件傳遞給 ViewModel。
  • ViewModel 包含將應用程式資料轉換為UI 狀態的邏輯,因為來自 Repository 的資料通常與需要顯示的資料格式不同。例如,您可能需要合併來自多個 Repository 的資料,或者您可能想過濾資料記錄列表。

View 和 ViewModel 應該是一對一的關係。

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

最簡單的說法是,ViewModel 管理 UI 狀態,View 顯示該狀態。使用 View 和 ViewModel,您的 UI 層可以在配置更改(如螢幕旋轉)期間維護狀態,並且您可以獨立於 Flutter Widget 測試 UI 的邏輯。

應用程式的一個功能是以使用者為中心的,因此由 UI 層定義。成對的ViewViewModel 的每個例項定義了您應用中的一個功能。這通常是您應用中的一個螢幕,但不必如此。例如,考慮登入和退出。登入通常在一個特定的螢幕上完成,該螢幕的唯一目的是為使用者提供登入方式。在應用程式程式碼中,登入螢幕將由一個 LoginViewModel 類和一個 LoginView 類組成。

另一方面,退出應用通常不是在一個專用螢幕上完成的。退出應用的功能通常以選單中的按鈕、使用者帳戶螢幕或任何其他不同位置的形式呈現給使用者。它通常在多個位置呈現。在這種情況下,您可能有一個 LogoutViewModel 和一個 LogoutView,它只包含一個可以放入其他 Widget 中的按鈕。

檢視

#

在 Flutter 中,View 是應用程式的 Widget 類。View 是渲染 UI 的主要方法,不應包含任何業務邏輯。它們應該從 ViewModel 接收所有渲染所需的資料。

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

View 應該包含的唯一邏輯是:

  • 簡單的 if 語句,根據 ViewModel 中的標誌或可空欄位來顯示和隱藏 Widget。
  • 動畫邏輯。
  • 基於裝置資訊(如螢幕大小或方向)的佈局邏輯。
  • 簡單的路由邏輯。

所有與資料相關的邏輯都應在 ViewModel 中處理。

ViewModel

#

ViewModel 暴露了渲染 View 所需的應用程式資料。在本頁面描述的架構設計中,您 Flutter 應用程式中的大部分邏輯都位於 ViewModel 中。

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

ViewModel 的主要職責包括:

  • 從 Repository 檢索應用程式資料,並將其轉換為適合在 View 中呈現的格式。例如,它可能會過濾、排序或聚合資料。
  • 維護 View 所需的當前狀態,以便 View 可以在不丟失資料的情況下重建。例如,它可能包含布林標誌來有條件地渲染 View 中的 Widget,或者一個欄位來跟蹤螢幕上輪播圖的哪個部分是活動的。
  • 向 View 公開回調(稱為 **Commands**),可以將其附加到事件處理程式,如按鈕點選或表單提交。

Commands 以 Command 模式命名,是 Dart 函式,允許 Views 在不瞭解其實現的情況下執行復雜邏輯。Commands 被編寫為 ViewModel 類中的成員,供 View 類中的手勢處理程式呼叫。

您可以在 應用架構案例研究UI 層 部分找到 View、ViewModel 和 Command 的示例。

要溫和地瞭解 Flutter 中的 MVVM,請檢視 狀態管理基礎知識

資料層

#

應用的資料層處理您的業務資料和邏輯。資料層由兩部分架構組成:Service 和 Repository。這些部分應具有定義明確的輸入和輸出,以簡化其可重用性和可測試性。

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

使用 MVVM 語言,Service 和 Repository 構成了您的Model 層

Repository

#

Repository 類是您的 Model 資料的真相來源。它們負責從 Service 拉取資料,並將原始資料轉換為領域模型 (domain models)。領域模型代表應用程式所需的資料,並以 View Model 類可以消費的方式進行格式化。您的應用中處理的每種不同型別的資料都應該有一個 Repository 類。

Repository 處理與 Service 相關的業務邏輯,例如:

  • 快取
  • 錯誤處理
  • 重試邏輯
  • 資料重新整理
  • 輪詢 Service 獲取新資料
  • 基於使用者操作重新整理資料
A simplified diagram of the architecture described on this page with the Repository object highlighted.

Repository 輸出應用程式資料為領域模型。例如,一個社交媒體應用可能有一個 UserProfileRepository 類,它公開一個 Stream<UserProfile?>,每當使用者登入或登出時就會發出一個新值。

Repository 輸出的模型被 ViewModel 消費。Repository 和 ViewModel 之間存在多對多關係。一個 ViewModel 可以使用多個 Repository 來獲取它需要的資料,一個 Repository 也可以被多個 ViewModel 使用。

Repository 之間永遠不應該相互瞭解。如果您的應用程式的業務邏輯需要來自兩個 Repository 的資料,您應該在 ViewModel 或領域層中組合資料,特別是如果您的 Repository-to-ViewModel 關係很複雜。

Service

#

Service 位於您應用程式的最底層。它們封裝 API 端點並公開非同步響應物件,如 FutureStream 物件。它們僅用於隔離資料載入,並且不包含狀態。您的應用應該為每個資料來源都有一個 Service 類。Service 可能封裝的端點示例包括:

  • 底層平臺,如 iOS 和 Android API。
  • REST 端點。
  • 本地檔案。

經驗法則,當所需資料位於您的應用程式的 Dart 程式碼之外時,Service 最有幫助——前面所有示例都是如此。

Service 和 Repository 之間存在多對多關係。單個 Repository 可以使用多個 Service,一個 Service 可以被多個 Repository 使用。

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

可選:領域層

#

隨著您的應用程式的增長和功能的增加,您可能需要抽象掉那些給 ViewModel 帶來過多複雜性的邏輯。這些類通常稱為 Interactor 或Use-case

Use-case 負責使 UI 層和資料層之間的互動更簡單、更可重用。它們從 Repository 獲取資料,並使其適用於 UI 層。

MVVM design pattern with an added domain layer object

Use-case 主要用於封裝原本會留在 ViewModel 中且滿足以下一個或多個條件的業務邏輯:

  1. 需要合併來自多個 Repository 的資料。
  2. 非常複雜。
  3. 該邏輯將被不同的 ViewModel 重用。

這一層是可選的,因為並非所有應用程式或應用程式中的功能都有這些要求。如果您認為您的應用程式會從這個額外的層中受益,請考慮其優缺點:

優點缺點
✅ 避免 ViewModel 中的程式碼重複。❌ 增加了架構的複雜性,增加了更多的類和更高的認知負荷。
✅ 透過將複雜的業務邏輯與 UI 邏輯分離來提高可測試性。❌ 測試需要額外的 Mock。
✅ 提高 ViewModel 中程式碼的可讀性。❌ 為您的程式碼增加了額外的樣板程式碼。

透過 Use-case 進行資料訪問

#

新增領域層的另一個考慮因素是 ViewModel 是否將繼續直接訪問 Repository 資料,還是您將強制 ViewModel 透過 Use-case 獲取資料。換句話說,您是根據需要新增 Use-case 嗎?也許當您注意到 ViewModel 中存在重複邏輯時?或者,您是每次 ViewModel 需要資料時都建立一個 Use-case,即使 Use-case 中的邏輯很簡單?

如果您選擇後者,它會加劇前面概述的優缺點。您的應用程式程式碼將高度模組化和可測試,但它也會增加大量的開銷。

一個好的方法是僅在需要時新增 Use-case。如果您發現您的 ViewModel 大部分時間都透過 Use-case 訪問資料,您可以隨時重構程式碼以專門使用 Use-case。本指南後面使用的示例應用程式為某些功能提供了 Use-case,但也讓 ViewModel 直接與 Repository 互動。一個複雜的功能最終可能看起來像這樣:

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

這種新增 Use-case 的方法由以下規則定義:

  • Use-case 依賴於 Repository。
  • Use-case 和 Repository 之間存在多對多關係。
  • ViewModel 依賴於一個或多個 Use-case *和* 一個或多個 Repository。

這種使用 Use-case 的方法看起來不像分層的千層麵,更像是一盤餐盤,有兩道主菜(UI 層和資料層)和一個配菜(領域層)。Use-case 只是具有定義明確的輸入和輸出的實用工具類。這種方法是靈活且可擴充套件的,但它需要更高的嚴謹性來保持秩序。

反饋

#

隨著本網站部分的不斷發展,我們歡迎您的反饋