Большие умные ViewModels, тупые Views и любая модель, лучший подход MVVM?
Следующий код является рефакторингом моего предыдущего подхода MVVM ( толстые модели, тощие модели ViewModels и немые представления, лучший подход MVVM?), В котором я переместил логику и реализацию INotifyPropertyChanged из модели обратно в ViewModel. Это имеет больше смысла, поскольку, как было указано, вам часто приходится использовать модели, которые вы либо не можете изменить, либо не хотите менять, и поэтому ваш подход MVVM должен быть в состоянии работать с любым классом модели, как это происходит с существовать.
Этот пример по-прежнему позволяет вам просматривать живые данные из вашей модели в режиме разработки в Visual Studio и Expression Blend, что, на мой взгляд, важно, поскольку у вас может быть фиктивное хранилище данных, к которому подключается дизайнер, например, с самыми маленькими и самыми большими строками, которые Пользовательский интерфейс может столкнуться с тем, чтобы он мог настроить дизайн на основе этих крайностей.
Вопросы:
- Я немного удивлен, что мне даже нужно "поставить таймер" в моей ViewModel, так как кажется, что это функция INotifyPropertyChanged, она выглядит избыточной, но это был единственный способ, которым я мог получить пользовательский интерфейс XAML постоянно (один раз в секунду) отражают состояние моей модели. Поэтому было бы интересно услышать любого, кто мог бы использовать этот подход, если вы столкнулись с какими-либо недостатками в будущем, например, с многопоточностью или производительностью.
Следующий код будет работать, если вы просто скопируете XAML и код в новый проект WPF.
XAML:
<Window x:Class="TestMvvm73892.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestMvvm73892"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ObjectDataProvider
x:Key="DataSourceCustomer"
ObjectType="{x:Type local:CustomerViewModel}"
MethodName="GetCustomerViewModel"/>
</Window.Resources>
<DockPanel DataContext="{StaticResource DataSourceCustomer}">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock Text="{Binding Path=FirstName}"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding Path=LastName}"/>
</StackPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock Text="{Binding Path=TimeOfMostRecentActivity}"/>
</StackPanel>
</DockPanel>
</Window>
Код позади:
using System;
using System.Windows;
using System.ComponentModel;
using System.Threading;
namespace TestMvvm73892
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
//view model
public class CustomerViewModel : INotifyPropertyChanged
{
private string _firstName;
private string _lastName;
private DateTime _timeOfMostRecentActivity;
private Timer _timer;
public string FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
this.RaisePropertyChanged("FirstName");
}
}
public string LastName
{
get
{
return _lastName;
}
set
{
_lastName = value;
this.RaisePropertyChanged("LastName");
}
}
public DateTime TimeOfMostRecentActivity
{
get
{
return _timeOfMostRecentActivity;
}
set
{
_timeOfMostRecentActivity = value;
this.RaisePropertyChanged("TimeOfMostRecentActivity");
}
}
public CustomerViewModel()
{
_timer = new Timer(CheckForChangesInModel, null, 0, 1000);
}
private void CheckForChangesInModel(object state)
{
Customer currentCustomer = CustomerViewModel.GetCurrentCustomer();
MapFieldsFromModeltoViewModel(currentCustomer, this);
}
public static CustomerViewModel GetCustomerViewModel()
{
CustomerViewModel customerViewModel = new CustomerViewModel();
Customer currentCustomer = CustomerViewModel.GetCurrentCustomer();
MapFieldsFromModeltoViewModel(currentCustomer, customerViewModel);
return customerViewModel;
}
public static void MapFieldsFromModeltoViewModel
(Customer model, CustomerViewModel viewModel)
{
viewModel.FirstName = model.FirstName;
viewModel.LastName = model.LastName;
viewModel.TimeOfMostRecentActivity = model.TimeOfMostRecentActivity;
}
public static Customer GetCurrentCustomer()
{
return Customer.GetCurrentCustomer();
}
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
//model
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime TimeOfMostRecentActivity { get; set; }
public static Customer GetCurrentCustomer()
{
return new Customer
{ FirstName = "Jim"
, LastName = "Smith"
, TimeOfMostRecentActivity = DateTime.Now
};
}
}
}
2 ответа
Мне нравится ваш пример выше, я думаю, что он воплощает дух MVVM. Просто для пояснения, однако, код ViewModel и код модели не должны находиться в том же исходном файле, что и реальный код позади. На самом деле, я бы сказал, что они не должны быть в одном проекте.
Вот MVVM, насколько я понимаю:
M - модель - это данные, возвращаемые из бизнес-уровня (BL). Это должно быть легко, содержать данные только для чтения. Классы Model являются глупыми и не содержат логику Update, Write или Delete и генерируются BL в результате запросов, команд, действий и т. Д. Классы Model не знают о потребностях в представлении приложения-потребителя, поэтому они могут быть использованы любым способом применения. Чтобы по-настоящему воспользоваться этим повторным использованием, мы хотим, чтобы классы Model были независимы от проекта пользовательского интерфейса.
VM - ViewModel содержит коммуникационный уровень: он выдает запросы BL и обрабатывает результаты способом, подходящим для представления. Как и в примере выше, он также получает модель и форматирует ее для конкретных потребностей презентации. Думайте об этом как о "Связующем классе". В приведенном выше примере данные просто перемещаются из одного объекта в другой, но ViewModel будет отвечать за такие вещи, как предоставление свойства типа "FullName" или добавление начальных нулей в ZipCode. Это верно, что класс привязки является тем, чтобы реализовать INotifyPropertyChanged. И снова, для повторного использования, я бы, вероятно, извлек этот слой в свой собственный проект. Это позволит вам экспериментировать с различными вариантами пользовательского интерфейса без каких-либо изменений в системе.
V - Вид связан с объектом класса Binding, созданным в ВМ. Вид очень тупой: он ничего не знает о BL или VM. Данные могут быть связаны в обоих направлениях, но виртуальная машина обрабатывает ошибки, проверку и т. Д. Любые операции синхронизации данных обрабатываются путем передачи запросов обратно в BL и повторной обработки результатов.
Это будет зависеть от типа приложения, но кажется трудным постоянно проверять модель, чтобы увидеть, изменилась ли она. Представьте, что вы подключаетесь к BL, который построил бизнес-объект (BO) из DAL, который подключается к БД. В этом сценарии вы бы постоянно воссоздали BO, который, я уверен, был бы убийцей производительности. Вы могли бы реализовать систему проверки на BL, которая прослушивала уведомления, или иметь метод для сравнения последнего известного времени изменения с фактическим, или вы могли бы кэшировать BO на BL. Просто несколько идей.
Также я сказал выше, что модель должна быть легкой. Есть тяжелые варианты, такие как CSLA, но я не уверен, насколько хорошо они вписываются в идею MVVM.
Я не хочу выдавать себя за эксперта, я до сих пор изучал эти идеи только при разработке архитектуры нашего нового программного обеспечения. Я хотел бы прочитать некоторые обсуждения по этой теме.
Мое личное мнение заключается в том, что, хотя модель следует использовать для загрузки и хранения данных, ответственность ViewModel заключается в том, чтобы знать, когда эти данные необходимы, поэтому имеет смысл использовать таймер в ViewModel. Таким образом, вы можете использовать вашу модель с другой моделью представления (для которой может быть достаточно извлечь данные только один раз, а не каждую секунду).
Несколько вещей для рассмотрения:
- Реализуйте модель для поддержки асинхронного извлечения данных (очень важно, если вы хотите использовать Silverlight)
- Будьте осторожны при обновлении коллекции из фонового потока (в вашем примере это не проблема, но если вам когда-нибудь понадобится использовать ObservableCollection, чем помнить, что его нельзя обновить из потока, не являющегося пользовательским интерфейсом, читайте больше здесь)