Co to jest MVVM?
- 7 min
Aplikacje .NET MAUI, które nie korzystają z wzorca Model-Widok-Model Widoku (MVVM), zwykle mają więcej kodu w plikach code-behind. Pliki związane z kodem w programie .NET MAUI postępują według tego wzorca: {something}.xaml.cs. Większość kodu w pliku kodu zaplecza zwykle kontroluje zachowanie interfejsu użytkownika. Zachowanie interfejsu użytkownika może obejmować dowolny element, który występuje w interfejsie użytkownika, na przykład zmiana koloru lub tekstu. I może zawierać wszystko, co się dzieje ze względu na interfejs użytkownika, w tym procedury obsługi kliknięć przycisków.
Jednym z problemów z tym podejściem jest to, że trudno jest napisać testy jednostkowe dla plików code-behind. Pliki zaplecza często przyjmują stan aplikacji utworzony przez analizowanie XAML lub nawet wpływ innych stron. Te warunki są trudne do obsługi w narzędziu do uruchamiania testów jednostkowych, które może nawet nie działać na urządzeniu mobilnym, nie wspominając już o interfejsie użytkownika. Dlatego testy jednostkowe rzadko mogą testować zachowania interfejsu użytkownika w tych plikach.
Ale oto, gdzie wzorzec MVVM jest przydatny. W przypadku poprawnego użycia wzorzec MVVM rozwiązuje te problemy, przenosząc większość logiki zachowania interfejsu użytkownika do klas z możliwością testowania jednostkowego, które są nazywane modelami widoków. Wzorzec MVVM jest najczęściej używany z platformami obsługującymi powiązanie danych. Za pomocą .NET MAUI można powiązać dane każdego elementu interfejsu użytkownika z viewmodel
i wyeliminować lub prawie wyeliminować kod w widoku lub kodzie ukrytym.
Jakie są części aplikacji MVVM?
viewmodel
Chociaż element jest unikatową częścią wzorca MVVM, wzorzec definiuje również część modelu i część widoku. Definicje tych części są zgodne z innymi typowymi wzorcami, takimi jak Model-View-Controller (MVC).
Co to jest model?
W aplikacji MVVM termin model jest używany do oznaczania danych i operacji biznesowych. Model nie wiąże się z prezentacją użytkownika aplikacji.
Przydatną regułą do określania, jaki kod należy do modelu, jest to, że powinien być przenośny na różnych platformach. Od aplikacji mobilnej, przez interfejs internetowy, aż po program wiersza poleceń, używając tego samego modelu we wszystkich przypadkach. Nie ma związku z tym, jak informacje są wyświetlane użytkownikowi.
Jeśli myślisz o aplikacji HR z naszego scenariusza, model może zawierać klasę Employee
i klasę Department
, która przechowuje dane i logikę tych jednostek. Model może również zawierać elementy takie jak klasa, która zawiera logikę EmployeeRepository
trwałości. Niektóre inne wzorce projektowe oprogramowania uwzględniają takie elementy, jak repozytoria , niezależnie od modelu. Jednak w kontekście MVVM często odnosimy się do dowolnej logiki biznesowej lub danych biznesowych w ramach modelu.
Oto dwa przykłady modelu w języku 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() { ... }
...
}
Co to jest widok?
Widok kodu steruje elementami, które bezpośrednio wchodzą w interakcję z użytkownikiem, takimi jak kontrolki, takie jak przyciski i pola wprowadzania, a także inne czysto wizualne elementy, takie jak motywy, style i czcionki.
W programie .NET MAUI nie trzeba pisać żadnego kodu w języku C#, aby samodzielnie wygenerować widok. Zamiast tego często definiuje się widoki według plików XAML. Istnieją sytuacje, które wymagają stworzenia niestandardowej kontrolki użytkownika, w których tworzysz własny widok za pomocą kodu.
Co to jest viewmodel
?
To prowadzi nas z powrotem do viewmodel
. Element viewmodel
pełni rolę pośrednika między naszą logiką biznesową (model) a naszymi widokami (UI).
Zastanów się, co viewmodel
może zrobić dla aplikacji HR. Załóżmy, że istnieje widok, który wyświetla dostępny czas urlopowy pracownika i chcesz, aby saldo urlopu było wyświetlane jako "2 tygodnie, 3 dni, 4 godziny". Jednak logika biznesowa w modelu zapewnia tę samą wartość co 13,5 dni, czyli liczbę dziesiętną reprezentującą łączną liczbę dni w 8-godzinnym dniu roboczym. Model obiektów może wyglądać podobnie do poniższej listy:
Model —
Employee
klasa zawierająca metodę:public decimal GetVacationBalanceInDays() { //Some math that calculates current vacation balance ... }
ViewModel —
EmployeeViewModel
klasa, która ma właściwość podobną do następującej: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" } } }
View — strona XAML zawierająca pojedynczą etykietę i przycisk zamknięcia. Etykieta ma powiązanie z właściwością
viewmodel
:<Label Text="{Binding FormattedVacationBalance}" />
Następnie wystarczy
BindingContext
, że strona zostanie ustawiona na wystąpienie klasyEmployeeViewModel
.
W tym przykładzie model zawiera logikę biznesową. Ta logika nie jest powiązana z wyświetlaczem czy urządzeniem. Można użyć tej samej logiki dla urządzenia przenośnego lub komputera stacjonarnego. Widok nie ma wiedzy na temat logiki biznesowej. Kontrolki widoku, takie jak etykieta, wiedzą, jak wyświetlić właściwy tekst na ekranie, ale nie ma znaczenia, czy jest to saldo urlopu, czy losowy ciąg znaków. Zna viewmodel
trochę obu światów, więc może działać jako pośrednik.
Co ciekawe, viewmodel
działa jako pośrednik, ujawniając właściwości, z którymi widok może się wiązać. Właściwości publiczne to jedyny sposób, w jaki viewmodel
dostarcza dane. Element viewmodel
jest tak zwany, ponieważ "model" w mvVM reprezentuje strukturę, dane i logikę procesów biznesowych, podczas gdy viewmodel
reprezentuje strukturę, dane i logikę wymaganą przez widok.
W jaki sposób widok działa z elementem viewmodel
?
Gdy przyjrzysz się relacji, jaką viewmodel
ma z modelem, jest to standardowy związek typu klasa-klasa. Obiekt viewmodel
ma instancję modelu i udostępnia aspekty modelu w widoku za pomocą właściwości. A jak widok pobiera i ustawia właściwości obiektu w viewmodel
? Kiedy viewmodel
się zmienia, jak widok aktualizuje kontrolki wizualne z nowymi wartościami? Odpowiedź to powiązanie danych.
Właściwości viewmodel
są odczytywane w momencie, gdy obiekt jest powiązany z widokiem. Jednak powiązanie nie ma możliwości poznania, czy jego właściwości viewmodel
zmienią się po zastosowaniu powiązania do widoku. Zmiana viewmodel
nie powoduje automatycznego propagowania nowej wartości przez powiązanie do widoku. Aby dokonać aktualizacji z viewmodel
do widoku, viewmodel
musi zaimplementować interfejs System.ComponentModel.INotifyPropertyChanged
.
Interfejs INotifyPropertyChanged
deklaruje jedno zdarzenie o nazwie PropertyChanged
. Przyjmuje on pojedynczy parametr, nazwę właściwości, która zmieniła jego wartość. System powiązań używany w programie .NET MAUI rozumie ten interfejs i nasłuchuje tego zdarzenia. Gdy właściwość na viewmodel
obiekcie ulega zmianie i zgłasza zdarzenie, powiązanie powiadamia element docelowy o zmianie.
Pomyśl o tym, jak ta relacja działa w aplikacji HR z pracownikiem viewmodel
. Obiekt viewmodel
ma właściwości reprezentujące pracownika, takie jak nazwa pracownika. Obiekt viewmodel
implementuje interfejs INotifyPropertyChanged
i gdy właściwość Name
ulegnie zmianie, zgłasza zdarzenie PropertyChanged
w sposób następujący:
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));
}
Widok opisujący szczegóły pracownika zawiera kontrolkę etykiety, która jest powiązana z właściwością viewmodel
pracownika Name
:
<Label Text="{Binding Name}" />
Gdy właściwość zmienia się w viewmodel
obiekcie, zdarzenie PropertyChanged
jest wywoływane z nazwą tej właściwości. Powiązanie nasłuchuje zdarzenia, a następnie powiadamia etykietę, że właściwość Name
została zmieniona. Następnie właściwość Text
etykiety zostanie zaktualizowana o najnowszą wartość.