Co to jest MVVM?
- 7 minut
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 —
Employeeklasa zawierająca metodę:public decimal GetVacationBalanceInDays() { //Some math that calculates current vacation balance ... }ViewModel —
EmployeeViewModelklasa, 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ą viewmodelpracownika Name :
<Label Text="{Binding Name}" />
Gdy właściwość zmienia się w Name obiekcie, zdarzenie viewmodel 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ść.