Что такое MVVM?
- 7 мин
Приложения .NET MAUI, которые не используют model-View-ViewModel (MVVM), обычно имеют больше кода в файлах программной части . Файлы кода в .NET MAUI соответствуют этому шаблону: {что}.xaml.cs. Основная часть кода в файле кода программной части обычно управляет поведением пользовательского интерфейса. К поведению пользовательского интерфейса можно отнести любые действия, происходящие с пользовательским интерфейсом, например изменение цвета или ввод текста. Оно может включать в себя все, что происходит из-за использования пользовательского интерфейса, например обработчики нажатий кнопки.
Одна из проблем этого подхода заключается в сложности написания модульных тестов для файлов кода программной части. Файлы программной части часто предполагают состояние приложения, созданное путем синтаксического анализа XAML или даже созданных другими страницами. Эти условия сложно учесть в средстве выполнения модульных тестов, которое даже может не запускаться на мобильном устройстве, не говоря уже о пользовательском интерфейсе. Таким образом, модульные тесты редко используются для тестирования поведения элементов пользовательского интерфейса в этих файлах.
Но вот где шаблон MVVM полезен. Правильно примененный шаблон MVVM решает эту проблему, перемещая большую часть логики поведения пользовательского интерфейса в поддерживающие модульное тестирование классы, называемые моделями представлений. Шаблон MVVM чаще всего используется с платформами, которые поддерживают привязку данных. С помощью .NET MAUI можно привязать каждый элемент пользовательского интерфейса к элементу пользовательского viewmodel
интерфейса и исключить или почти исключить код в представлении или коде позади.
Из каких частей состоит приложение MVVM?
viewmodel
Хотя это уникальная часть шаблона MVVM, шаблон также определяет часть модели и часть представления. Их определения соответствуют некоторым другим шаблонам, таким как "Модель — представление — контроллер (MVC)".
Что такое модель?
В приложении MVVM модель терминов используется для обозначения бизнес-данных и операций. Модель не связана с презентацией пользователя приложения.
Полезное правило для определения того, какой код принадлежит модели, заключается в том, что он должен быть переносимым на разных платформах. Из мобильного приложения в веб-интерфейс или даже программу командной строки, используя одну и ту же модель во всех экземплярах. Она не связана с отображением информации пользователю.
Если говорить о приложении для отдела кадров из нашего сценария, модель может содержать класс 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
посредник между бизнес-логикой (моделью) и нашими представлениями (пользовательский интерфейс).
Подумайте о том, что viewmodel
может сделать для приложения отдела кадров. Предположим, что есть представление, которое отображает доступное время отпуска сотрудника, и вы хотите, чтобы баланс отпуска отображался как "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
вызывает событие, привязка уведомляет целевой объект изменения.
Подумайте о том, как эта связь работает в приложении отдела кадров с сотрудником 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
обновляется с помощью последнего значения.