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 Cars { get; set; } public CarViewModel() { Cars = new ObservableCollection { 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 Cars { get; set; } public Car SelectedCar { get { return _selectedCar; } set { _selectedCar = value; OnPropertyChanged("SelectedCar"); } } public CarViewModel() { Cars = new ObservableCollection { 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 представление, это окно приложения.
При «строгой» реализации в коде окна присутствует только инициализация модели представления в конструкторе.
1 2 3 4 5 |
<strong>public MainWindow() { InitializeComponent(); DataContext = new CarViewModel(); }</strong> |
При «нестрогой» добавляются методы событийной модели для работы с данными. В данном случае это обработчики кликов для кнопок добавления и удаления автомобилей соответственно.
1 2 3 4 5 6 7 8 |
<strong>private void Add_Click(object sender, RoutedEventArgs e) { ((CarViewModel)DataContext).AddCar(); } private void Delete_Click(object sender, RoutedEventArgs e) { ((CarViewModel)DataContext).DeleteCar(); }</strong> |
Связывание конкретный элементов управления с данными осуществляется в XAML.
Сам механизм связывания мы подробно рассматривать не будем. Также, как и механизм команд, это тема для отдельной статьи. Приведём лишь пример разметки, которая отображает каталог автомобилей в ListBox, а информацию о выбранном автомобиле (выбранный мышью элемент в ListBox).
1 2 |
<button click="Add_Click">+</button> <button click="Delete_Click">-</button> |
На скриншоте показана работа программы, которая была написана в качестве примера для данной статьи.
Резюме
Паттерн MVVM хорошо подходит для десктоп приложений на платформах поддерживающих связывание и которые имеют сложную бизнес-логику.
Он позволяет добиться значительно большей гибкости, расширяемости и в немалой степени облегчает сам процесс разработки. Так же, как и в MVC в MVVM для модели и модели представления можно применять такие паттерны, как супертип слоя, отложенная инициализация, фабрика и др. Кроме того, применяя MVVM можно практически полностью разделить работу дизайнера и программиста.
Но, всё это хорошо для достаточно сложных проектов на соответствующих платформах.
Для простых программ MVVM (также, как и MVC) создаёт лишь ничем неоправданные сложности. А, если платформа не поддерживает связывание (например, Windows Forms), то сама реализация паттерна MVVM усложняется на порядок и в этом случае гораздо эффективнее использовать другие паттерны, которым связывание не требуется.
Источники
- Model-View-ViewModel (Википедия)
- Model-View-Controller (Википедия)
Добавить комментарий