- Регистрация
- 9 Май 2015
- Сообщения
- 1,071
- Баллы
- 155
- Возраст
- 51
Model-View-ViewModel (MVVM) – архитектурный паттерн, ориентированный главным образом на платформы, поддерживающие связывание данных и элементов пользовательского интерфейса (например, WPF).
MVVM сравнительно «молодой» паттерн (впервые представлен Джоном Госсманом в 2005 году [1]).
Описание паттерна MVVM
MVVM преследует те же цели, что и MVC. Разделить бизнес-логику и пользовательский интерфейс. Однако в MVC изменения, производимые пользователем при работе с интерфейсом, не влияют непосредственно на модель, а предварительно обрабатываются Контроллером. В MVVM происходит двустороннее связывание.
Различие между этими паттернами лучше можно схематично представить следующими диаграммами.
Паттерн MVVM
Паттерн MVC
Паттерн MVVM состоит из следующих компонентов:
Применительно к WPF, окно приложения, это представление (всегда). Остальные компоненты паттерна принято реализовывать в виде не визуальных классов. Подробнее реализация MVVM в WPF будет рассмотрена далее на прикладном примере.
Пример реализации
Допустим, нам необходимо реализовать каталог автомобилей, который содержит информацию о моделях автомобилей, их максимальной скорости и стоимости.
Модель
Создадим класс, который описывает автомобиль и будет являться моделью в нашем приложении.
Для обеспечения возможности связывания требуется, чтобы можно было отслеживать изменения в модели. Поэтому её класс должен реализовывать интерфейс INotifyPropertyChanged (пространство имён System.ComponentModel).
Ниже приведён код этого класса:
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.
В классе модели представления создадим закрытое поле и свойство для работы с выбранным из каталога автомобилем.
private Car _selectedCar;
public Car SelectedCar
{
get { return _selectedCar; }
set
{
_selectedCar = value;
OnPropertyChanged("SelectedCar");
}
}
Сам каталог будет представлен в виде коллекции, которую для простоты будем заполнять в конструкторе.
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 }
};
}
Также включим в модель представления два метода. Для добавления нового автомобиля и удаления уже существующего.
public void AddCar()
{
Car car = new Car();
Cars.Insert(0, car);
SelectedCar = car;
}
public void DeleteCar()
{
if (_selectedCar != null)
{
Cars.Remove(SelectedCar);
}
}
Методов для редактирования не добавляем так как необходимый функционал полностью обеспечивается за счёт связывания.
«Строгий» и «нестрогий» варианты реализации
При строгом следовании паттерну MVVM, добавление и удаление также должны быть реализованы через связывание с помощью механизма команд. Это практически полностью избавляет представление о программные логики, но сильно усложняет и без того не простую архитектуру. Поэтому, в статье рассмотрен несколько упрощённый («нестрогий») вариант реализации.
Ниже приведён полный исходный код класса модели представления.
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 представление, это окно приложения.
«Строгий» и «нестрогий» варианты реализации
При «строгой» реализации в коде окна присутствует только инициализация модели представления в конструкторе.
public MainWindow()
{
InitializeComponent();
DataContext = new CarViewModel();
}
При «нестрогой» добавляются методы событийной модели для работы с данными. В данном случае это обработчики кликов для кнопок добавления и удаления автомобилей соответственно.
private void Add_Click(object sender, RoutedEventArgs e)
{
((CarViewModel)DataContext).AddCar();
}
private void Delete_Click(object sender, RoutedEventArgs e)
{
((CarViewModel)DataContext).DeleteCar();
}
Связывание конкретный элементов управления с данными осуществляется в XAML.
Сам механизм связывания мы подробно рассматривать не будем. Также, как и механизм команд, это тема для отдельной статьи. Приведём лишь пример разметки, которая отображает каталог автомобилей в ListBox, а информацию о выбранном автомобиле (выбранный мышью элемент в ListBox).
<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 усложняется на порядок и в этом случае гораздо эффективнее использовать другие паттерны, которым связывание не требуется.
Источники
MVVM сравнительно «молодой» паттерн (впервые представлен Джоном Госсманом в 2005 году [1]).
Описание паттерна MVVM
MVVM преследует те же цели, что и MVC. Разделить бизнес-логику и пользовательский интерфейс. Однако в MVC изменения, производимые пользователем при работе с интерфейсом, не влияют непосредственно на модель, а предварительно обрабатываются Контроллером. В MVVM происходит двустороннее связывание.
Различие между этими паттернами лучше можно схематично представить следующими диаграммами.
Паттерн MVVM
Паттерн MVC
Паттерн MVVM состоит из следующих компонентов:
- Модель (англ. Model)
Представляет собой бизнес-логику приложения; - Представление (англ. View)
Графический интерфейс для работы с данными или их отображения (окна, кнопки, таблицы и т.д.); - Модель представления (англ. ViewModel)
Обёртка подлежащих связыванию данных из модели, которая содержит методы, которые используются представлением для работы с моделью.
Применительно к WPF, окно приложения, это представление (всегда). Остальные компоненты паттерна принято реализовывать в виде не визуальных классов. Подробнее реализация MVVM в WPF будет рассмотрена далее на прикладном примере.
Пример реализации
Допустим, нам необходимо реализовать каталог автомобилей, который содержит информацию о моделях автомобилей, их максимальной скорости и стоимости.
Модель
Создадим класс, который описывает автомобиль и будет являться моделью в нашем приложении.
Для обеспечения возможности связывания требуется, чтобы можно было отслеживать изменения в модели. Поэтому её класс должен реализовывать интерфейс INotifyPropertyChanged (пространство имён System.ComponentModel).
Ниже приведён код этого класса:
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.
В классе модели представления создадим закрытое поле и свойство для работы с выбранным из каталога автомобилем.
private Car _selectedCar;
public Car SelectedCar
{
get { return _selectedCar; }
set
{
_selectedCar = value;
OnPropertyChanged("SelectedCar");
}
}
Сам каталог будет представлен в виде коллекции, которую для простоты будем заполнять в конструкторе.
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 }
};
}
Также включим в модель представления два метода. Для добавления нового автомобиля и удаления уже существующего.
public void AddCar()
{
Car car = new Car();
Cars.Insert(0, car);
SelectedCar = car;
}
public void DeleteCar()
{
if (_selectedCar != null)
{
Cars.Remove(SelectedCar);
}
}
Методов для редактирования не добавляем так как необходимый функционал полностью обеспечивается за счёт связывания.
«Строгий» и «нестрогий» варианты реализации
При строгом следовании паттерну MVVM, добавление и удаление также должны быть реализованы через связывание с помощью механизма команд. Это практически полностью избавляет представление о программные логики, но сильно усложняет и без того не простую архитектуру. Поэтому, в статье рассмотрен несколько упрощённый («нестрогий») вариант реализации.
Ниже приведён полный исходный код класса модели представления.
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 представление, это окно приложения.
«Строгий» и «нестрогий» варианты реализации
При «строгой» реализации в коде окна присутствует только инициализация модели представления в конструкторе.
public MainWindow()
{
InitializeComponent();
DataContext = new CarViewModel();
}
При «нестрогой» добавляются методы событийной модели для работы с данными. В данном случае это обработчики кликов для кнопок добавления и удаления автомобилей соответственно.
private void Add_Click(object sender, RoutedEventArgs e)
{
((CarViewModel)DataContext).AddCar();
}
private void Delete_Click(object sender, RoutedEventArgs e)
{
((CarViewModel)DataContext).DeleteCar();
}
Связывание конкретный элементов управления с данными осуществляется в XAML.
Сам механизм связывания мы подробно рассматривать не будем. Также, как и механизм команд, это тема для отдельной статьи. Приведём лишь пример разметки, которая отображает каталог автомобилей в ListBox, а информацию о выбранном автомобиле (выбранный мышью элемент в ListBox).
<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 усложняется на порядок и в этом случае гораздо эффективнее использовать другие паттерны, которым связывание не требуется.
Источники
- (Википедия)
- (Википедия)