Model-View-ViewModel (MVVM) – архитектурный паттерн, ориентированный главным образом на платформы, поддерживающие связывание данных и элементов пользовательского интерфейса (например, WPF).
MVVM сравнительно «молодой» паттерн (впервые представлен Джоном Госсманом в 2005 году [1]).
Описание паттерна MVVM
MVVM преследует те же цели, что и MVC. Разделить бизнес-логику и пользовательский интерфейс. Однако в MVC изменения, производимые пользователем при работе с интерфейсом, не влияют непосредственно на модель, а предварительно обрабатываются Контроллером. В MVVM происходит двустороннее связывание.
Различие между этими паттернами лучше можно схематично представить следующими диаграммами.


Паттерн MVVM состоит из следующих компонентов:
- Модель (англ. Model)
Представляет собой бизнес-логику приложения; - Представление (англ. View)
Графический интерфейс для работы с данными или их отображения (окна, кнопки, таблицы и т.д.); - Модель представления (англ. ViewModel)
Обёртка подлежащих связыванию данных из модели, которая содержит методы, которые используются представлением для работы с моделью.
Применительно к WPF, окно приложения, это представление (всегда). Остальные компоненты паттерна принято реализовывать в виде не визуальных классов. Подробнее реализация MVVM в WPF будет рассмотрена далее на прикладном примере.
Пример реализации
Допустим, нам необходимо реализовать каталог автомобилей, который содержит информацию о моделях автомобилей, их максимальной скорости и стоимости.
Модель
Создадим класс, который описывает автомобиль и будет являться моделью в нашем приложении.
Для обеспечения возможности связывания требуется, чтобы можно было отслеживать изменения в модели. Поэтому её класс должен реализовывать интерфейс INotifyPropertyChanged (пространство имён System.ComponentModel).
Ниже приведён код этого класса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
class Car : INotifyPropertyChanged { private string _model; private int _maxSpeed; private decimal _price; public string Model { get { return _model; } set { _model = value; OnPropertyChanged("Model"); } } public int MaxSpeed { get { return _maxSpeed; } set { _maxSpeed = value; OnPropertyChanged("MaxSpeed"); } } public decimal Price { get { return _price; } set { _price = value; OnPropertyChanged("Price"); } } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged([CallerMemberName]string prop = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); } } |
Если не считать требования по реализации интерфейса INotifyPropertyChanged, то, как и в большинстве случаев, данная модель представляет собой обычный «сущностный» класс.
Модель представления
Модель представления является по сути посредником между представлением и моделью. Для поддержки связывания модель представления также должна реализовывать интерфейс INotifyPropertyChanged.
В классе модели представления создадим закрытое поле и свойство для работы с выбранным из каталога автомобилем.
1 2 3 4 5 6 7 8 9 10 |
private Car _selectedCar; public Car SelectedCar { get { return _selectedCar; } set { _selectedCar = value; OnPropertyChanged("SelectedCar"); } } |
Сам каталог будет представлен в виде коллекции, которую для простоты будем заполнять в конструкторе.
1 2 3 4 5 6 7 8 9 10 |
public ObservableCollection<Car> Cars { get; set; } public CarViewModel() { Cars = new ObservableCollection<Car> { new Car { Model="ВАЗ-2105", MaxSpeed=150, Price=56000 }, new Car { Model="LADA Priora", MaxSpeed=170, Price=560000 }, new Car { Model="КамАЗ", MaxSpeed=100, Price=5600000 } }; } |
Также включим в модель представления два метода. Для добавления нового автомобиля и удаления уже существующего.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public void AddCar() { Car car = new Car(); Cars.Insert(0, car); SelectedCar = car; } public void DeleteCar() { if (_selectedCar != null) { Cars.Remove(SelectedCar); } } |
Методов для редактирования не добавляем так как необходимый функционал полностью обеспечивается за счёт связывания.
При строгом следовании паттерну MVVM, добавление и удаление также должны быть реализованы через связывание с помощью механизма команд. Это практически полностью избавляет представление о программные логики, но сильно усложняет и без того не простую архитектуру. Поэтому, в статье рассмотрен несколько упрощённый («нестрогий») вариант реализации.
Ниже приведён полный исходный код класса модели представления.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
class CarViewModel : INotifyPropertyChanged { private Car _selectedCar; public ObservableCollection<Car> Cars { get; set; } public Car SelectedCar { get { return _selectedCar; } set { _selectedCar = value; OnPropertyChanged("SelectedCar"); } } public CarViewModel() { Cars = new ObservableCollection<Car> { new Car { Model="ВАЗ-2105", MaxSpeed=150, Price=56000 }, new Car { Model="LADA Priora", MaxSpeed=170, Price=560000 }, new Car { Model="КамАЗ", MaxSpeed=100, Price=5600000 } }; } public void AddCar() { Car car = new Car(); Cars.Insert(0, car); SelectedCar = car; } public void DeleteCar() { if (_selectedCar != null) { Cars.Remove(SelectedCar); } } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged([CallerMemberName]string prop = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); } } |
Представление
Как уже говорилось ранее, в случае WPF представление, это окно приложения.
При «строгой» реализации в коде окна присутствует только инициализация модели представления в конструкторе. При «нестрогой» добавляются методы событийной модели для работы с данными. В данном случае это обработчики кликов для кнопок добавления и удаления автомобилей соответственно.
Связывание конкретный элементов управления с данными осуществляется в XAML.
Сам механизм связывания мы подробно рассматривать не будем. Также, как и механизм команд, это тема для отдельной статьи. Приведём лишь пример разметки, которая отображает каталог автомобилей в ListBox, а информацию о выбранном автомобиле (выбранный мышью элемент в ListBox).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<Window x:Class="MVVM_Example.MainWindow" xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml xmlns:d=http://schemas.microsoft.com/expression/blend/2008 xmlns:mc=http://schemas.openxmlformats.org/markup-compatibility/2006 xmlns:local="clr-namespace:MVVM_Example" mc:Ignorable="d" Title="MVVM_Example" Height="403.921" Width="310.294"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <StackPanel Grid.Column="0" DataContext="{Binding SelectedCar}" Grid.ColumnSpan="2"> <TextBlock Text="Выбранный элемент" Margin="0,0,-233,0" /> <TextBlock Text="Модель" /> <TextBox Text="{Binding Model, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock Text="Максимальная скрорость, км/ч" /> <TextBox Text="{Binding MaxSpeed, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock Text="Цена, руб." /> <TextBox Text="{Binding Price, UpdateSourceTrigger=PropertyChanged}" /> <Button Click="Add_Click">+</Button> <Button Click="Delete_Click" >-</Button> </StackPanel> <ListBox Grid.Column="0" Grid.Row="0" ItemsSource="{Binding Cars}" SelectedItem="{Binding SelectedCar}" Grid.ColumnSpan="2" Margin="0,170,0,0"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Margin="5"> <TextBlock FontSize="18" Text="{Binding Path=Model}" /> <TextBlock Text="{Binding Path=MaxSpeed}" /> <TextBlock Text="{Binding Path=Price}" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </Window> |
На скриншоте показана работа программы, которая была написана в качестве примера для данной статьи.
Резюме
Паттерн MVVM хорошо подходит для десктоп приложений на платформах поддерживающих связывание и которые имеют сложную бизнес-логику.
Он позволяет добиться значительно большей гибкости, расширяемости и в немалой степени облегчает сам процесс разработки. Так же, как и в MVC в MVVM для модели и модели представления можно применять такие паттерны, как супертип слоя, отложенная инициализация, фабрика и др. Кроме того, применяя MVVM можно практически полностью разделить работу дизайнера и программиста.
Но, всё это хорошо для достаточно сложных проектов на соответствующих платформах.
Для простых программ MVVM (также, как и MVC) создаёт лишь ничем неоправданные сложности. А, если платформа не поддерживает связывание (например, Windows Forms), то сама реализация паттерна MVVM усложняется на порядок и в этом случае гораздо эффективнее использовать другие паттерны, которым связывание не требуется.
Источники
- Model-View-ViewModel (Википедия)
- Model-View-Controller (Википедия)
Добавить комментарий