绑定到Model或ViewModel

我知道已经有关于这个主题的问题,但是那些问题在某种程度上与其他问题有关,并没有提供确凿的答案。

特别是那里: 问题1 , 问题2 ,当然还有问题3 所以请不要太快关闭这个问题 。 他们回答那里只是说“做这个,做那个”而不是为什么!

有些人否认需要ViewModel并且说“标准”方式是直接绑定到Model 。 这是我否认并试图用技术论证来certificate的。

从我在MVCMVPPresentation Model背景来看,使用ViewModel对我来说很自然。 也许我错过了一个重点?

所以对我来说,默认是绑定到ViewModel ,无论Model是什么(无论它是否实现了INotifyPropertyChanged )。

我看到绑定到ViewModel原因几个 ,包括(如此处提到的CodeProject和另一篇文章 )

1.从视图中删除逻辑

  • 使逻辑单元可测试
  • 减少代码冗余(在需要时复制)

2.安全

  • 该模型包含用户不得更改的属性
  • 如果绑定到模型,则会发生自动但不需要的更新

3.松耦合

  • 如果直接绑定到模型,则较低层和View之间将存在耦合
  • 更改模型会导致所有视图中的更改
  • 视图不依赖于任何给定的模型
  • 可以使用EF,某些DSL,批处理文件等轻松生成模型

4.发展速度

  • 您可以从Prototype ViewModel层次结构开始并绑定到该层次结构
  • 如果模型仍在开发中,您可以从Prototype Model开始
  • ModelViewModel可以开发testdriven,无论View如何
  • View可以完全由设计师或具有强大设计背景的开发人员构建

5.解决了“棘手的同步”问题

  • 对于任何给定的“棘手同步”问题,有很多解决方案,例如
  • AutoMapper
  • 模型中的事件系统(模型触发事件,ViewModel订阅)

6.整个项目的平等结构

  • 有一些点需要ViewModel,比如SelectedItem
  • 将Binding与Model和ViewModel混合是错误的
  • 新鲜的开发人员很难弄清楚项目的结构
  • 稍后开始带ViewModel时,无法绕过它是凌乱的

7.可扩展性

  • 让我们定义:如果你不使用ViewModel,它不是MVVM
  • MVVM可以很容易地被用于许多数据源,很多视图
  • 如果发现任何性能问题:延迟加载和缓存都在ViewModel中

8.分离关切

  • 掌握复杂性是软件中的主要问题
  • ViewModels全权负责推动变革
  • 将通知发送到视图就像将其推送到不同的进程或计算机一样容易
  • ViewModel,而不是模型/数据源上的更改通知的View寄存器
  • 数据源可以忽略向导致更改的ViewModel发送事件

相反,来自另一个线程的人倾倒了一些点,包括

  1. 如果直接更新模型,则视图模型将不知道触发属性更改事件。 这会导致UI不同步。 这严重限制了在父视图模型和子视图模型之间发送消息的选项。

  2. 如果模型有自己的属性更改通知,则#1和2不是问题。 相反,如果包装器VM超出范围但模型没有,则必须担心内存泄漏。

  3. 如果你的模型很复杂,有很多子对象,那么你必须遍历整个树并创建第二个对象图,它会遮蔽第一个。 这可能非常繁琐且容易出错。

  4. 包裹的集合特别难以使用。 任何时候(UI或后端)从集合中插入或删除项目,都需要更新影子集合以匹配。 这种代码真的很难搞定。

所以,问题是:绑定的默认方式是什么?为什么?

我是否会错过必须拥有ViewModel的点数?

是否有任何想要绑定到模型的真正原因?

最重要的是为什么 ,而不是如何。

视图模型通常包含成员,这些成员用于视图(例如, IsSomethingSelectedIsSomethingExpandedIsSomethingVisibleICommand任何实现等属性)。

你认为有什么理由将所有这些东西带入模特吗? 当然不。 这就是视图模型存在的原因。

所以,问题是:绑定的默认方式是什么?为什么?

一般来说,我认为拥有ViewModel并绑定它是默认设置。 有一个原因是“ViewModel”存在并且是MVVM模式的一部分。

除了纯粹的数据之外,ViewModel还有其他原因。 您还通常实现特定于应用程序的逻辑(即:不是模型的一部分,但在应用程序中需要)。 例如,任何ICommand实现都应该在ViewModel上,因为它与Model完全无关。

是否有任何想要绑定到模型的真正原因?

在某些情况下,它可能更简单,特别是如果您的模型已经实现了INotifyPropertyChanged 。 降低代码复杂性是一个有价值的目标,具有自己的优点。

我同意Reed– ViewModel是应该绑定的。 我总是把模型想象成一个或多或少静态的值集合,它们可能不像ViewModel那样频繁或动态地改变。 通常,我尝试在模型中放置任何可以在编译时假定的值,以及在ViewModel中在运行时确定的任何值。

View本身不应该只有最简单的基本逻辑。 其余应该是对ViewModel的引用。 安全性有时是个问题,但我喜欢这样做只是为了代码的可读性和简洁性。 当在视图中完成所有美学事物时,处理代码要容易得多,所有更多数学,逻辑事物都隐藏在ViewModel中,并且所有硬数据都在一个单独的模型中。

MVVM也与MVC密切相关,其指导原则是模型和视图不应直接相互看到。 再一次,对我而言,这是一个清晰的事情。 决定模型值应该如何变化的逻辑也应该在ViewModel / Controller中。 观点不应该自己思考。

将View视为接待员:它是一个友好的面孔,可以与用户进行交互(“对话”)。 ViewModel是前台旁边门后办公室的会计师,Model是他/她的参考书和笔记集。 如果接待员开始在会计师账簿的边缘写作,擦除会计师的笔记,并更改记录中的内容,事情开始变得混乱。

你的问题没有“正确”的答案。 当然,WPF会愉快地允许您绑定到您自己声明的任何“模型”对象; 框架根本不关心。 您并不总是必须遵循MVVM模式,因为您正在WPF中执行应用程序。 上下文始终是您编写的任何软件的关键。 如果您时间紧迫并且需要快速完成原型,请务必绑定到模型并在需要时重构。

所以我认为你真正要问的是“我什么时候应该使用MVVM模式?”

答案当然是“当你的问题符合模式时”。

那么MVVM模式给你带来了什么? 您已经列出了使用MVVM的几个原因,但对于该模式最重要的原因是松散耦合 – 所有其余的sorta都带有它。

MVVM模式的重点是确保您的模型是一个状态机,它不知道数据如何呈现给用户或从用户获得数据。 在某种程度上,您的模型是以对模型有意义的格式构建的纯数据,而不是以对人类有意义的格式构建。 您的ViewModel负责在纯数据平台(Model)和用户输入平台(View)之间进行转换。 ViewModel与View和Model紧密耦合,但重要的是Model不知道View。

相反的论点:

  1. 从视图中删除逻辑

从视图模型中删除逻辑同样有用。 通过将validation,计算字段等逻辑推入模型,您可以获得更轻,更清晰的视图模型。

•使逻辑单元可测试

模型本身很容易进行unit testing。 您不必担心模拟库以及使用处理外部服务的视图模型的情况。

•减少代码冗余(在需要时复制)

多个视图模型可以共享相同的模型,减少validation冗余,计算字段等。

  1. 安全性•模型包含用户不得更改的属性

然后不要在UI上公开它们。

•自动,但如果绑定到模型,可能会发生不需要的更新

这一点都没有任何意义。 如果您的虚拟机只是模型的包装器,那么它只会将这些更新推送下来。

  1. 松耦合•如果直接绑定到模型,下层和View之间会有耦合

当你在它们之间推送一个包装器VM时,这种耦合不会神奇地消失。

•更改模型会导致所有视图中的更改

更改模型会导致所有包装器视图模型发生更改。 更改视图模型还会导致所有视图发生更改。 因此,模型仍然可以导致所有视图的更改。

•视图不依赖于任何给定的模型

无论是否包含模型的视图模型都是如此。 它只能看到属性,而不是实际的类。

•可以使用EF,某些DSL,批处理文件等轻松生成模型

是的。 通过一些工作,这些易于生成的模型可以包含有用的接口,如INotifyDataErrorInfo,IChangeTracking和IEditableObject。

  1. 发展速度

绑定到模型可以提供更快的开发,因为您不必映射所有属性。

•您可以从Prototype ViewModel层次结构开始并绑定到该层次结构

或者我可以从原型模型开始。 添加包装器没有任何好处。

•如果模型仍处于开发阶段,您可以从原型模型开始•模型和ViewModel可以开发testdriven,无论View是什么

同样,通过在模型周围添加包装器没有任何结果。

•View可以完全由设计师或具有强大设计背景的开发人员构建

再一次,通过在模型周围添加包装器没有任何好处。

  1. 解决了“棘手的同步”问题•对于任何给定的“棘手的同步”问题,有很多解决方案,例如•AutoMapper

如果使用automapper将数据复制到视图模型中,那么您不使用MVVM模式。 您只是使用视图和模型。

•模型中的事件系统(模型触发事件,ViewModel订阅)

你好内存泄漏。 也就是说,除非你非常小心并且放弃了跨多个视图共享模型的能力。

  1. 整个项目中的平等结构•有些点需要ViewModel,比如SelectedItem

无关紧要。 没有人反对非包装视图模型。

•将Binding与Model和ViewModel混合是错误的

不支持。

•新鲜的开发人员很难弄清楚项目的结构

不支持。

•稍后开始使用ViewModel时无法解决它是混乱的

无关紧要。 同样,没有人反对使用非包装视图模型。

  1. 可伸缩性•让我们定义:如果您不使用ViewModel,它不是MVVM

无关紧要。 第三次,没有人反对使用非包装视图模型。

•MVVM可以轻松地用于大量数据源,大量视图

无关紧要。 我们不是在争论是否使用MVVM,我们正在争论如何最好地使用它。

•如果发现任何性能问题:延迟加载和缓存都在ViewModel中

同意,但无关紧要。 没有人建议您将服务呼叫推送到模型中。

  1. 关注点分离

这是包装器视图模型最难落下的地方。

视图模型已经必须处理UI数据(例如模式,所选项)并托管调用外部服务的ICommands。

将所有模型数据,validation逻辑,计算属性等推入视图模型使得更加臃肿。

这是一个简单的对象图。 只是一些非常简单的模型,具有正常的属性更改和validation事件

那些认为模型需要包含在视图模型中的人会显示您的代码。

 public class ModelBase : INotifyPropertyChanged, INotifyDataErrorInfo { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); OnErrorChanged(propertyName); } protected void OnErrorChanged(string propertyName) { if (ErrorsChanged != null) ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); } public event EventHandler ErrorsChanged; public virtual IEnumerable GetErrors(string propertyName) { return Enumerable.Empty(); } public virtual bool HasErrors { get { return false; } } } public class Customer : ModelBase { public Customer() { Orders.CollectionChanged += Orders_CollectionChanged; } void Orders_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.OldItems.Count > 0) foreach (INotifyPropertyChanged item in e.OldItems) item.PropertyChanged -= Customer_PropertyChanged; if (e.NewItems.Count > 0) foreach (INotifyPropertyChanged item in e.NewItems) item.PropertyChanged += Customer_PropertyChanged; OnPropertyChanged("TotalSales"); } void Customer_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "Total") OnPropertyChanged("TotalSales"); } public decimal TotalSales { get { return Orders.Sum(o => o.Total); } } private string _FirstName; public string FirstName { get { return _FirstName; } set { if (_FirstName == value) return; _FirstName = value; OnPropertyChanged(); } } private string _LastName; public string LastName { get { return _LastName; } set { if (_LastName == value) return; _LastName = value; OnPropertyChanged(); } } private readonly ObservableCollection _Orders = new ObservableCollection(); public ObservableCollection Orders { get { return _Orders; } } } public class Order : ModelBase { public Order() { OrderLines.CollectionChanged += OrderLines_CollectionChanged; } void OrderLines_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.OldItems.Count > 0) foreach (INotifyPropertyChanged item in e.OldItems) item.PropertyChanged -= OrderLine_PropertyChanged; if (e.NewItems.Count > 0) foreach (INotifyPropertyChanged item in e.NewItems) item.PropertyChanged += OrderLine_PropertyChanged; OnPropertyChanged("Total"); OnErrorChanged(""); } public override bool HasErrors { get { return GetErrors("").OfType().Any() || OrderLines.Any(ol => ol.HasErrors); } } void OrderLine_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "Extension") OnPropertyChanged("Total"); } public decimal Total { get { return OrderLines.Sum(o => o.Extension); } } private int _OrderNumber; private DateTime _OrderDate; public DateTime OrderDate { get { return _OrderDate; } set { if (_OrderDate == value) return; _OrderDate = value; OnPropertyChanged(); } } public int OrderNumber { get { return _OrderNumber; } set { if (_OrderNumber == value) return; _OrderNumber = value; OnPropertyChanged(); } } private readonly ObservableCollection _OrderLines = new ObservableCollection(); public ObservableCollection OrderLines { get { return _OrderLines; } } } public class OrderLine : ModelBase { private string _ProductName; private decimal _Quantity; private decimal _Price; public decimal Price { get { return _Price; } set { if (_Price == value) return; _Price = value; OnPropertyChanged(); } } public string ProductName { get { return _ProductName; } set { if (_ProductName == value) return; _ProductName = value; OnPropertyChanged(); OnPropertyChanged("Extension"); } } public decimal Quantity { get { return _Quantity; } set { if (_Quantity == value) return; _Quantity = value; OnPropertyChanged(); OnPropertyChanged("Extension"); } } public decimal Extension { get { return Quantity * Price; } } public override IEnumerable GetErrors(string propertyName) { var result = new List(); if ((propertyName == "" || propertyName == "Price") && Price < 0) result.Add("Price is less than 0."); if ((propertyName == "" || propertyName == "Quantity") && Quantity < 0) result.Add("Quantity is less than 0."); return result; } public override bool HasErrors { get { return GetErrors("").OfType().Any(); } } } 

以下是一个典型的ViewModel:

 public class CustomerViewModel : ModelBase { public CustomerViewMode() { LoadCustomer = null; //load customer from service, database, repositry, etc. SaveCustomer = null; //save customer to service, database, repositry, etc. } private Customer _CurrentCustomer; public Customer CurrentCustomer { get { return _CurrentCustomer; } set { if (_CurrentCustomer == value) return; _CurrentCustomer = value; OnPropertyChanged(); } } public ICommand LoadCustomer { get; private set; } public ICommand SaveCustomer { get; private set; } }