應用程式架構指南
以下頁面示範如何使用最佳實務建構應用程式。本指南中的建議可以應用於大多數應用程式,使其更容易擴展、測試和維護。然而,它們是指導方針,而非鐵律,您應該根據您的獨特需求進行調整。
本節提供 Flutter 應用程式架構方式的高階概述。它說明了應用程式的各個層級,以及存在於每個層級中的類別。接下來的章節將提供具體的程式碼範例,並逐步說明如何實作這些建議的 Flutter 應用程式。
專案結構總覽
#關注點分離是設計 Flutter 應用程式時最重要的原則。您的 Flutter 應用程式應分為兩個廣泛的層級:UI 層和資料層。
每個層級會進一步拆分為不同的元件,每個元件都有不同的職責、明確定義的介面、邊界和依賴關係。本指南建議您將應用程式拆分為以下元件
- 檢視
- 檢視模型
- 儲存庫
- 服務
MVVM
#如果您接觸過 Model-View-ViewModel 設計模式 (MVVM),您會覺得這很熟悉。MVVM 是一種將應用程式的功能分為三個部分:Model
、ViewModel
和 View
的設計模式。View 和 view model 組成應用程式的 UI 層。Repositories 和 services 代表應用程式的資料,或 MVVM 的模型層。每個元件都會在下一節中定義。
應用程式中的每個功能都會包含一個 view 來描述 UI 和一個 view model 來處理邏輯、一個或多個 repositories 作為應用程式資料的事實來源,以及零個或多個與外部 API (例如客戶端伺服器和平台外掛程式) 互動的 services。
應用程式的單一功能可能需要以下所有物件
每個物件和連接它們的箭頭將在本頁末尾詳細說明。在本指南中,以下簡化版的圖表將用作錨點。
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 的關係。
簡單來說,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 傳遞它們需要渲染的所有資料。
view 應包含的唯一邏輯是
- 基於 view model 中的旗標或可為 null 的欄位來顯示和隱藏 widget 的簡單 if 語句
- 動畫邏輯
- 基於裝置資訊 (例如螢幕大小或方向) 的版面配置邏輯。
- 簡單的路由邏輯
所有與資料相關的邏輯都應在 view model 中處理。
檢視模型
#view model 會公開渲染 view 所需的應用程式資料。在本頁描述的架構設計中,您 Flutter 應用程式中的大多數邏輯都存在於 view model 中。
view model 的主要職責包括
- 從 repositories 擷取應用程式資料,並將其轉換為適合在 view 中呈現的格式。例如,它可能會篩選、排序或彙總資料。
- 維護 view 中需要的目前狀態,以便 view 可以在不遺失資料的情況下重建。例如,它可能包含條件式渲染 view 中 widget 的布林值旗標,或追蹤螢幕上哪個輪播區段處於作用中的欄位。
- 向 view 公開可以附加到事件處理常式 (例如按鈕按下或表單提交) 的回呼 (稱為 指令)。
指令是根據 命令模式 命名的,並且是 Dart 函式,允許 view 在不了解其實作的情況下執行複雜的邏輯。指令會寫成 view model 類別的成員,以便由 view 類別中的手勢處理常式呼叫。
您可以在UI 層的 應用程式架構案例研究 部分找到 view、view model 和指令的範例。
如需 Flutter 中 MVVM 的簡要介紹,請參閱狀態管理基礎知識。
資料層
#應用程式的資料層會處理您的商業資料和邏輯。兩個架構部分組成了資料層:services 和 repositories。這些部分應具有明確定義的輸入和輸出,以簡化其可重複使用性和可測試性。
使用 MVVM 語言,services 和 repositories 組成您的模型層。
儲存庫
#Repository 類別是您模型資料的事實來源。它們負責輪詢來自 services 的資料,並將原始資料轉換為網域模型。網域模型表示應用程式所需的資料,其格式設定方式可讓您的 view model 類別使用。每個應用程式中處理的不同資料類型都應該有一個 repository 類別。
Repositories 處理與 services 相關聯的商業邏輯,例如
- 快取
- 錯誤處理
- 重試邏輯
- 重新整理資料
- 輪詢 services 以取得新資料
- 根據使用者動作重新整理資料
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 端點,並公開非同步回應物件,例如 Future
和 Stream
物件。它們僅用於隔離資料載入,並且不保留任何狀態。您的應用程式每個資料來源應該有一個 service 類別。Services 可能包裝的端點範例包括
- 基礎平台,例如 iOS 和 Android API
- REST 端點
- 本機檔案
根據經驗法則,當必要的資料存在於您的應用程式的 Dart 程式碼之外時,services 最有幫助 - 這對於前面的每個範例都是如此。
Services 和 repositories 具有多對多的關係。一個 Repository 可以使用多個 services,而一個 service 可以被多個 repositories 使用。
選用:網域層
#隨著您的應用程式成長並新增功能,您可能需要抽離邏輯,這些邏輯會為您的 view model 新增太多複雜性。這些類別通常稱為 interactors 或 use-cases。
Use-cases 負責簡化 UI 和資料層之間的互動,並使其更具可重複使用性。它們會從 repositories 取得資料,並使其適合 UI 層使用。
Use-cases 主要用於封裝否則會存在於 view model 中且符合以下一個或多個條件的商業邏輯
- 需要合併來自多個 repositories 的資料
- 非常複雜
- 該邏輯將被不同的 view model 重複使用
此層為可選,因為並非所有應用程式或應用程式中的功能都有這些需求。如果您懷疑您的應用程式會因此額外層而受益,請考慮其優缺點
優點 | 缺點 |
---|---|
✅ 避免在視圖模型中重複程式碼 | ❌ 增加架構的複雜性,新增更多類別並增加認知負擔 |
✅ 透過將複雜的商業邏輯與 UI 邏輯分離來提高可測試性 | ❌ 測試需要額外的模擬 |
✅ 提高視圖模型中程式碼的可讀性 | ❌ 在您的程式碼中增加額外的樣板程式碼 |
使用案例的資料存取
#新增領域層的另一個考量是,視圖模型是否將繼續直接存取儲存庫資料,或者您是否將強制視圖模型透過用例來取得其資料。換句話說,您會根據需要新增用例嗎?也許當您注意到視圖模型中有重複邏輯時?或者,您是否會在每次視圖模型需要資料時建立用例,即使該用例中的邏輯很簡單?
如果您選擇後者,它會加劇前面概述的優缺點。您的應用程式程式碼將極其模組化且可測試,但也會增加大量不必要的負擔。
一個好的方法是僅在需要時才新增用例。如果您發現視圖模型大部分時間都是透過用例來存取資料,您可以隨時重構程式碼以完全使用用例。本指南稍後使用的範例應用程式在某些功能上使用了用例,但也有些視圖模型直接與儲存庫互動。一個複雜的功能最終可能會看起來像這樣
這種新增用例的方法由以下規則定義
- 用例依賴儲存庫
- 用例和儲存庫具有多對多關係
- 視圖模型依賴一個或多個用例以及一個或多個儲存庫
這種使用用例的方法看起來比較不像分層的千層麵,而更像是一道有兩道主菜(UI 和資料層)和一道配菜(領域層)的餐盤晚餐。用例只是具有明確定義的輸入和輸出的實用類別。這種方法具有彈性且可擴展,但需要更勤奮地維護秩序。
意見反應
#由於本網站的此部分正在發展中,我們歡迎您的回饋!
除非另有說明,否則本網站上的文件反映了 Flutter 的最新穩定版本。頁面最後更新時間為 2024-12-04。 檢視原始碼 或 回報問題。