跳到主要內容

應用程式架構指南

以下頁面示範如何使用最佳實務建構應用程式。本指南中的建議可以應用於大多數應用程式,使其更容易擴展、測試和維護。然而,它們是指導方針,而非鐵律,您應該根據您的獨特需求進行調整。

本節提供 Flutter 應用程式架構方式的高階概述。它說明了應用程式的各個層級,以及存在於每個層級中的類別。接下來的章節將提供具體的程式碼範例,並逐步說明如何實作這些建議的 Flutter 應用程式。

專案結構總覽

#

關注點分離是設計 Flutter 應用程式時最重要的原則。您的 Flutter 應用程式應分為兩個廣泛的層級:UI 層和資料層。

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

  • 檢視
  • 檢視模型
  • 儲存庫
  • 服務

MVVM

#

如果您接觸過 Model-View-ViewModel 設計模式 (MVVM),您會覺得這很熟悉。MVVM 是一種將應用程式的功能分為三個部分:ModelViewModelView 的設計模式。View 和 view model 組成應用程式的 UI 層。Repositories 和 services 代表應用程式的資料,或 MVVM 的模型層。每個元件都會在下一節中定義。

MVVM design pattern

應用程式中的每個功能都會包含一個 view 來描述 UI 和一個 view model 來處理邏輯、一個或多個 repositories 作為應用程式資料的事實來源,以及零個或多個與外部 API (例如客戶端伺服器和平台外掛程式) 互動的 services。

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

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 設計模式的兩個架構元件組成

  • Views 描述如何向使用者呈現應用程式資料。具體來說,它指的是*組成一個功能的 widget 組合*。例如,view 通常(但不總是)是一個具有 Scaffold widget 以及 widget 樹中其下方所有 widget 的螢幕。View 也負責將事件傳遞給 view model 以回應使用者互動。
  • View models 包含將應用程式資料轉換為UI 狀態的邏輯,因為來自 repositories 的資料通常與需要顯示的資料格式不同。例如,您可能需要合併來自多個 repositories 的資料,或者您可能想要篩選資料記錄列表。

View 和 view model 應具有 1:1 的關係。

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

簡單來說,view model 管理 UI 狀態,而 view 顯示該狀態。使用 view 和 view model,您的 UI 層可以在組態變更期間 (例如螢幕旋轉) 維持狀態,並且您可以獨立於 Flutter widget 測試 UI 的邏輯。

應用程式的功能是以使用者為中心的,因此由 UI 層定義。每個 view 和 view model 的配對實例都會定義您應用程式中的一個功能。這通常是您應用程式中的一個螢幕,但它不一定是。例如,考慮登入和登出。登入通常在一個專用螢幕上完成,其唯一目的是為使用者提供登入的方式。在應用程式程式碼中,登入畫面將由 LoginViewModel 類別和 LoginView 類別組成。

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

檢視

#

在 Flutter 中,view 是您應用程式的 widget 類別。View 是呈現 UI 的主要方法,不應包含任何商業邏輯。它們應該從 view model 傳遞它們需要渲染的所有資料。

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

view 應包含的唯一邏輯是

  • 基於 view model 中的旗標或可為 null 的欄位來顯示和隱藏 widget 的簡單 if 語句
  • 動畫邏輯
  • 基於裝置資訊 (例如螢幕大小或方向) 的版面配置邏輯。
  • 簡單的路由邏輯

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

檢視模型

#

view model 會公開渲染 view 所需的應用程式資料。在本頁描述的架構設計中,您 Flutter 應用程式中的大多數邏輯都存在於 view model 中。

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

view model 的主要職責包括

  • 從 repositories 擷取應用程式資料,並將其轉換為適合在 view 中呈現的格式。例如,它可能會篩選、排序或彙總資料。
  • 維護 view 中需要的目前狀態,以便 view 可以在不遺失資料的情況下重建。例如,它可能包含條件式渲染 view 中 widget 的布林值旗標,或追蹤螢幕上哪個輪播區段處於作用中的欄位。
  • 向 view 公開可以附加到事件處理常式 (例如按鈕按下或表單提交) 的回呼 (稱為 指令)。

指令是根據 命令模式 命名的,並且是 Dart 函式,允許 view 在不了解其實作的情況下執行複雜的邏輯。指令會寫成 view model 類別的成員,以便由 view 類別中的手勢處理常式呼叫。

您可以在UI 層應用程式架構案例研究 部分找到 view、view model 和指令的範例。

如需 Flutter 中 MVVM 的簡要介紹,請參閱狀態管理基礎知識

資料層

#

應用程式的資料層會處理您的商業資料和邏輯。兩個架構部分組成了資料層:services 和 repositories。這些部分應具有明確定義的輸入和輸出,以簡化其可重複使用性和可測試性。

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

使用 MVVM 語言,services 和 repositories 組成您的模型層

儲存庫

#

Repository 類別是您模型資料的事實來源。它們負責輪詢來自 services 的資料,並將原始資料轉換為網域模型。網域模型表示應用程式所需的資料,其格式設定方式可讓您的 view model 類別使用。每個應用程式中處理的不同資料類型都應該有一個 repository 類別。

Repositories 處理與 services 相關聯的商業邏輯,例如

  • 快取
  • 錯誤處理
  • 重試邏輯
  • 重新整理資料
  • 輪詢 services 以取得新資料
  • 根據使用者動作重新整理資料
A simplified diagram of the architecture described on this page with the Repository object highlighted.

Repositories 會將應用程式資料輸出為網域模型。例如,社群媒體應用程式可能會有一個 UserProfileRepository 類別,該類別會公開一個 Stream<UserProfile?>,當使用者登入或登出時,該類別會發出新值。

repositories 輸出的模型會由 view model 使用。Repositories 和 view model 具有多對多的關係。View model 可以使用多個 repositories 來取得所需的資料,而一個 repository 可以被多個 view model 使用。

Repositories 彼此之間永遠不應知道。如果您的應用程式具有需要來自兩個 repositories 的資料的商業邏輯,則您應該在 view model 或網域層中合併資料,特別是如果您的 repository 到 view model 的關係很複雜。

服務

#

Services 位於應用程式的最底層。它們會包裝 API 端點,並公開非同步回應物件,例如 FutureStream 物件。它們僅用於隔離資料載入,並且不保留任何狀態。您的應用程式每個資料來源應該有一個 service 類別。Services 可能包裝的端點範例包括

  • 基礎平台,例如 iOS 和 Android API
  • REST 端點
  • 本機檔案

根據經驗法則,當必要的資料存在於您的應用程式的 Dart 程式碼之外時,services 最有幫助 - 這對於前面的每個範例都是如此。

Services 和 repositories 具有多對多的關係。一個 Repository 可以使用多個 services,而一個 service 可以被多個 repositories 使用。

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

選用:網域層

#

隨著您的應用程式成長並新增功能,您可能需要抽離邏輯,這些邏輯會為您的 view model 新增太多複雜性。這些類別通常稱為 interactors 或 use-cases

Use-cases 負責簡化 UI 和資料層之間的互動,並使其更具可重複使用性。它們會從 repositories 取得資料,並使其適合 UI 層使用。

MVVM design pattern with an added domain layer object

Use-cases 主要用於封裝否則會存在於 view model 中且符合以下一個或多個條件的商業邏輯

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

此層為可選,因為並非所有應用程式或應用程式中的功能都有這些需求。如果您懷疑您的應用程式會因此額外層而受益,請考慮其優缺點

優點缺點
✅ 避免在視圖模型中重複程式碼❌ 增加架構的複雜性,新增更多類別並增加認知負擔
✅ 透過將複雜的商業邏輯與 UI 邏輯分離來提高可測試性❌ 測試需要額外的模擬
✅ 提高視圖模型中程式碼的可讀性❌ 在您的程式碼中增加額外的樣板程式碼

使用案例的資料存取

#

新增領域層的另一個考量是,視圖模型是否將繼續直接存取儲存庫資料,或者您是否將強制視圖模型透過用例來取得其資料。換句話說,您會根據需要新增用例嗎?也許當您注意到視圖模型中有重複邏輯時?或者,您是否會在每次視圖模型需要資料時建立用例,即使該用例中的邏輯很簡單?

如果您選擇後者,它會加劇前面概述的優缺點。您的應用程式程式碼將極其模組化且可測試,但也會增加大量不必要的負擔。

一個好的方法是僅在需要時才新增用例。如果您發現視圖模型大部分時間都是透過用例來存取資料,您可以隨時重構程式碼以完全使用用例。本指南稍後使用的範例應用程式在某些功能上使用了用例,但也有些視圖模型直接與儲存庫互動。一個複雜的功能最終可能會看起來像這樣

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

這種新增用例的方法由以下規則定義

  • 用例依賴儲存庫
  • 用例和儲存庫具有多對多關係
  • 視圖模型依賴一個或多個用例以及一個或多個儲存庫

這種使用用例的方法看起來比較不像分層的千層麵,而更像是一道有兩道主菜(UI 和資料層)和一道配菜(領域層)的餐盤晚餐。用例只是具有明確定義的輸入和輸出的實用類別。這種方法具有彈性且可擴展,但需要更勤奮地維護秩序。

意見反應

#

由於本網站的此部分正在發展中,我們歡迎您的回饋