Model臃肿,ViewModel瘦身,View愚钝,最佳MVVM实践?

21

这个问题上得到了慷慨的帮助,我创建了以下MVVM结构,它可以实时显示模型中的更改(当前日期/时间),非常好。

这种设置的一个很酷的优点是,当您在Visual Studio或Blend的设计模式下查看视图时,您将看到时间正在滴答作响,这意味着在设计时您可以访问来自模型的实时数据。

在使其正常工作的过程中,我惊讶地发现大部分代码从ViewModel移动到了Model中,包括实现INotifyPropertyChange。 另一个变化是,我不再绑定到ViewModel上的属性,而是绑定到方法

因此,当前这是我最喜欢的MVVM结构:

  1. View是愚蠢的:

    • 每个对象所需的ObjectDataProvider都有一个
    • 每个ObjectDataProvider映射到ViewModel上的一个方法(而不是属性)
    • XAML元素中没有x:Name属性
  2. ViewModel瘦身:

    • ViewModel中唯一的内容是视图绑定到的方法
  3. Model臃肿:

    • 模型在其每个属性上实现INotifyPropertyChanged。
    • 对于ViewModel中的每个方法(例如GetCurrentCustomer),模型都有一个相应的单例方法(例如GetCurrentCustomer)。
    • 模型处理此示例中的任何实时线程功能。

问题:

  1. 那些在实际情况下实施MVVM的人,您是否也采用了这种基本结构?如果不是,您的结构有何变化?
  2. 如何扩展此结构以包括路由命令和路由事件?

如果您只复制XAML和代码到新的WPF项目中,以下代码将正常工作。

XAML:

<Window x:Class="TestBinding99382.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TestBinding99382"
    Title="Window1" Height="300" Width="300">

    <Window.Resources>
        <ObjectDataProvider 
             x:Key="DataSourceCustomer" 
             ObjectType="{x:Type local:ShowCustomerViewModel}" 
                        MethodName="GetCurrentCustomer"/>
    </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.Windows;
using System.ComponentModel;
using System;
using System.Threading;

namespace TestBinding99382
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }
    }

    //view model
    public class ShowCustomerViewModel
    {
        public Customer GetCurrentCustomer() {
            return Customer.GetCurrentCustomer();
        }
    }

    //model
    public class Customer : INotifyPropertyChanged
    {
        private string _firstName;
        private string _lastName;
        private DateTime _timeOfMostRecentActivity;
        private static Customer _currentCustomer;
        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 Customer()
        {
            _timer = new Timer(UpdateDateTime, null, 0, 1000);
        }

        private void UpdateDateTime(object state)
        {
            TimeOfMostRecentActivity = DateTime.Now;
        }

        public static Customer GetCurrentCustomer()
        {
            if (_currentCustomer == null)
            {
                _currentCustomer = new Customer 
                     {  FirstName = "Jim"
                        , LastName = "Smith"
                        , TimeOfMostRecentActivity = DateTime.Now 
                     };
            }
            return _currentCustomer;
        }

        //INotifyPropertyChanged implementation
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }
}

关于RelayCommands和RoutedCommands,您可能想查看这些答案:https://dev59.com/WHRB5IYBdhLWcg3wWGEH#6441472 - Marc
2个回答

31
以下是我的看法,仅供参考:
我不太赞同你所提出的方法(除了"愚蠢视图"之外)。在现实生活中,您经常需要使用现有模型:它可能是您没有时间(或意愿)更改的旧代码,或者是您没有代码的库。在我看来,模型应完全不知道它将如何显示,并且应易于在非 WPF 应用程序中使用。因此,它不必实现任何特定接口,例如 INotifyPropertyChangedINotifyCollectionChanged,以使其在 MVVM 中可用。我认为与 UI 相关的所有逻辑都应驻留在 ViewModel 中。
关于RoutedEventsRoutedCommands,它们不太适合与 MVVM 模式一起使用。我通常尝试尽可能少地使用 RoutedEvents,根本不使用 RoutedCommands。相反,我的 ViewModels 公开 RelayCommand 属性,我将其绑定到 XAML 中的 UI 上(请参见 Josh Smith 的这篇文章了解有关 RelayCommand 的详细信息)。当我真正需要处理某些控件的事件时,我使用附加行为将事件映射到 ViewModel 命令(请查看Marlon Grech 的实现)。
所以,总之:
  • 愚蠢视图
  • 大而聪明的 ViewModel
  • 任何您想要或必须使用的模型
当然这只是我的方法,可能不是最好的方法,但我对它感觉相当舒适 ;)

非常有见地,特别是关于模型的部分,你应该能够使用MVVM模式来连接旧项目中没有WPF知识的数据类。有趣的是,你认为RoutedEvents和RoutedCommands并不真正适合MVVM,我原以为它们是用来在解耦模式(如MVVM)中使用的。感谢您的反馈。 - Edward Tanguay
我根据这个反馈重构了这个示例,将逻辑放到了ViewModel中: https://dev59.com/Q0fRa4cB1Zd3GeqP-JF9 - Edward Tanguay

3

我同意Thomas的看法。 对于任何关于WPF架构的建议:

  • 使用纯POCO实体,不带INotifyPropertyChange、状态跟踪、BL等。
  • 简单且小型的ViewModel通知视图实时更新。
  • 简单且可重用的UI,配合智能导航系统以避免复杂的数据层次和底层复杂的ViewModel。
  • 采用View First方法的MVVM以保持依赖项简单。
  • 使用Tasks或Rx进行异步操作。
  • 使用简单主题。
  • 不要使用复杂而稳健的UI,保持简单,只需利用WPF的UI组合和绑定功能即可。
  • 毫不犹豫地使用代码后台动态生成内容(表单、列表等),并节省大量声明式配置时间(适用于大多数情况)——对我来说是2015年必要的工作。使用扩展方法创建Fluent API。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接