Большие умные ViewModels, тупые Views и любая модель, лучший подход MVVM? - PullRequest
8 голосов
/ 13 мая 2009

Следующий код является рефакторингом моего предыдущего подхода MVVM ( Fat Models, тощие 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 ]

13 голосов
/ 13 мая 2009

Мне нравится ваш пример выше, я думаю, что он воплощает дух 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.

Я не хочу выдавать себя за эксперта, я до сих пор изучал эти идеи только при разработке архитектуры нашего нового программного обеспечения. Я хотел бы прочитать некоторые обсуждения по этой теме.

3 голосов
/ 13 мая 2009

Мое личное мнение состоит в том, что хотя Модель должна использоваться для загрузки и хранения данных, ViewModel несет ответственность за то, чтобы знать, когда эти данные необходимы, поэтому использование таймера в ViewModel имеет смысл , Таким образом, вы можете использовать вашу модель с другой ViewModel (для которой может быть достаточно извлечь данные только один раз, а не каждую секунду).

Несколько вещей для рассмотрения:

  • Реализация вашей модели для поддержки Асинхронный поиск данных (очень важно, если вы хотите использовать Silverlight)
  • Будьте осторожны при обновлении коллекции из фонового потока (в вашем примере это не проблема, но если вам когда-нибудь понадобится использовать ObservableCollection, помните, что его нельзя обновить из потока, не являющегося пользовательским интерфейсом, подробнее здесь )
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...