什麼是 MVVM?
不使用 MVVM 的 .NET MAUI 應用程式,在其「程式碼後置」檔案中往往會有更多程式碼。 .NET MAUI 中的程式碼後置檔案會遵循此模式:{something}.xaml.cs。 大部分程式碼後置檔案程式碼通常會控制使用者介面 (UI) 的行為。 「UI 行為」可以包含 UI 發生的任何內容,例如變更色彩或一些文字。 它可以包括因為 UI 而發生的任何事情,包括按鈕點擊處理常式。
此方法的一個問題是很難針對程式碼後置檔案撰寫單元測試。 程式碼後置檔案通常會假設應用程式狀態是透過剖析 XAML 所建立,或甚至是由其他頁面所建立。 這些條件對於未在行動裝置上執行的單元測試執行器而言已經相當難以處理,更別提具有使用者介面的案例了。 因此,單元測試很少能夠測試這些檔案中的 UI 行為。
但以下是 MVVM 模式很有用的地方。 正確使用時,MVVM 模式會藉由將大多數 UI 行為邏輯移至名為 ViewModel 的單元測試類別,來解決上述問題。 MVVM 模式最常搭配支援資料繫結的架構使用。 這是因為使用 .NET MAUI 時,您可以將每個 UI 元素針對 viewmodel
進行資料繫結,並消除或幾乎消除位於檢視或程式碼後置中的程式碼。
MVVM 應用程式的組件有哪些?
雖然 viewmodel
是 MVVM 模式的獨特組件,但該模式也會定義模型組件和檢視組件。 這些組件的定義與其他一些常見模式一致,例如 Model-View-Controller (MVC)。
模型是什麼?
在 MVVM 應用程式中,模型一詞用來表示您的商務資料和作業。 模型本身不參與應用程式的使用者簡報。
判斷哪個程式碼屬於模型的有用規則,是其應該可以跨不同平台移植。 從行動應用程式到 Web 介面,甚至命令列程式,在所有執行個體中使用相同的「模型」。 其與向使用者顯示資訊的方式無關。
當您考慮我們案例中的 HR 應用程式時,該模型可能包括保留那些實體之相關資料和邏輯的 Employee
類別和 Department
類別。 該模型也可能包括保留持續性邏輯的 EmployeeRepository
類別之類的項目。 一些其他軟體設計模式可能會將「存放庫」之類的項目與模型分開考慮。 但在 MVVM 的內容中,我們通常會將所有商務邏輯或商務資料視為模型的一部分。
以下是以 C# 撰寫之模型的兩個範例:
public class Employee
{
public int Id { get; }
public string Name { get; set; }
public Employee Supervisor { get; set; }
public DateTime HireDate { get; set; }
public void ClockIn() { ... }
}
public class EmployeeRepository
{
public IList<Employee> QueryEmployees() { ... }
...
}
檢視是什麼?
「檢視」程式碼可控制直接與使用者互動的項目,例如按鈕和輸入欄位等控制項,以及其他純視覺化元素,例如佈景主題、樣式和字型。
在 .NET MAUI 中,您不必撰寫任何 C# 程式碼,即可自行產生檢視。 相反地,您通常會透過 XAML 檔案定義檢視。 當然,有些情況會呼叫自訂使用者控制項,您可以在其中透過程式碼建立自己的檢視。
什麼是 viewmodel
?
我們回頭繼續探討 viewmodel
。 viewmodel
是商務邏輯 (模型) 與檢視 (UI) 之間的媒介。
想想 viewmodel
可能針對 HR 應用程式做什麼。 假設有一個檢視顯示員工可用的休假時間,而您希望假期餘額顯示為「2 周、3 天、4 小時」。但模型中的商業規則會提供與 13.5 天相同的值,這是十進位數,代表 8 小時工作日的總天數。 物件模型可能看起來像是下列清單:
模型 -
Employee
類別,其中包括一個方法:public decimal GetVacationBalanceInDays() { //Some math that calculates current vacation balance ... }
ViewModel -
EmployeeViewModel
類別,其有如下的屬性:public class EmployeeViewModel { private Employee _model; public string FormattedVacationBalance { get { decimal vacationBalance = _model.GetVacationBalanceInDays(); ... // Some logic to format and return the string as "X weeks, Y days, Z hours" } } }
檢視 - 包含單一標籤和關閉按鈕的 XAML 頁面。 此標籤會繫結至
viewmodel
的屬性:<Label Text="{Binding FormattedVacationBalance}" />
然後,您只需要將頁面的
BindingContext
設定為EmployeeViewModel
的執行個體。
在此範例中,模型包含「商務邏輯」。 此邏輯不會繫結至視覺化顯示或裝置。 您可以針對掌上型裝置或桌上型電腦使用相同的邏輯。 檢視對商務邏輯一無所知。 檢視控制項 (例如標籤) 知道如何在畫面上取得文字,但不在乎其是假期餘額或隨機字串。 viewmodel
對這兩種方式「略有所知」,因此其可以作為媒介。
有趣的是 viewmodel
如何完成其媒介的角色:它會向檢視可以繫結的項目公開屬性。 公用屬性是 viewmodel
提供資料的「唯一」方式。 這讓我們知道為什麼它會「稱為」viewmodel
。 MVVM 中的「模型」代表商務程序的結構、資料和邏輯,而 viewmodel
則表示檢視所需結構、資料和邏輯。
檢視如何與 viewmodel
搭配運作?
當您查看 viewmodel
與模型的關聯性時,其是標準類別對類別關聯性。 viewmodel
具有模型的執行個體,並透過屬性將模型的各個層面公開至檢視。 但是,檢視如何在 viewmodel
上取得和設定屬性? 當 viewmodel
變更時,檢視如何以新值更新視覺化控制項? 答案是資料繫結。
viewmodel
的屬性會在物件繫結至檢視時讀取。 但是,繫結無法得知 viewmodel
的屬性在繫結套用至檢視之後是否變更。 變更 viewmodel
不會透過繫結自動將新值傳播至檢視。 若要從 viewmodel
更新至檢視,viewmodel
必須實作 System.ComponentModel.INotifyPropertyChanged
介面。
INotifyPropertyChanged
介面會宣告名為 PropertyChanged
的單一事件。 其會採用單一參數,即變更其值的屬性名稱。 .NET MAUI 中使用的繫結系統會了解此介面,並接聽該事件。 當屬性在 viewmodel
上變更並引發事件時,繫結會通知變更的目標。
想想這在 HR 應用程式中如何與員工 viewmodel
搭配運作。 viewmodel
具有代表員工的屬性,例如員工的名稱。 viewmodel
會實作 INotifyPropertyChanged
介面,並在 Name
屬性變更時,引發 PropertyChanged
事件,如下所示:
using System.ComponentModel;
public class EmployeeViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private Employee _model;
public string Name
{
get {...}
set
{
_model.Name = value;
OnPropertyChanged(nameof(Name))
}
}
protected void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
描述員工詳細資料的檢視包含繫結至 viewmodel
的 Name
屬性的標籤控制項:
<Label Text="{Binding Name}" />
當 Name
屬性在 viewmodel
中變更時,系統會以該屬性的名稱引發 PropertyChanged
事件。 繫結會接聽事件,接著通知標籤 Name
屬性已變更。 然後,會以最新的值更新標籤的 Text
屬性。