【问题标题】:MVVM - PropertyChanged in Model or ViewModel?MVVM - 模型或视图模型中的 PropertyChanged?
【发布时间】:2013-05-27 16:58:39
【问题描述】:

我已经阅读了一些 MVVM 教程,并且我已经看到了这两种方式。大多数人将 ViewModel 用于 PropertyChanged(这是我一直在做的),但我遇到了一个在 Model 中这样做的。两种方法都可以接受吗?如果是这样,不同方法的优点/缺点是什么?

【问题讨论】:

  • Model 仅仅意味着拥有应用程序的业务逻辑。最好在 ViewModel 中有绑定属性,这将解耦业务实体和视图
  • 我就是这么想的。当我看到模型中的一个示例绑定时,我感到很困惑。
  • @Coder。数据绑定基础设施已经提供了足够多的“解耦”。将所有实体包装在伪视图模型中只会导致内存泄漏、UI/数据不一致和其他细微的错误。为抽象而添加抽象根本不是一个好主意。

标签: c# mvvm


【解决方案1】:

微软的模式和实践,MVVM 的发明者,我都不同意所选择的答案。

通常,模型实现了可以轻松绑定到视图的工具。这通常意味着它通过 INotifyPropertyChanged 和 INotifyCollectionChanged 接口支持属性和集合更改通知。表示对象集合的模型类通常派生自 ObservableCollection 类,该类提供 INotifyCollectionChanged 接口的实现。

-- 微软模式和实践:http://msdn.microsoft.com/en-us/library/gg405484%28v=pandp.40%29.aspx#sec4

此时数据绑定开始发挥作用。在简单的示例中,视图是直接绑定到模型的数据。模型的一部分通过单向数据绑定简单地显示在视图中。模型的其他部分可以通过直接将控件双向绑定到数据来进行编辑。例如,模型中的布尔值可以是绑定到 CheckBox 的数据,或者是绑定到 TextBox 的字符串字段。

-- MVVM 的发明者 John Gossman:http://blogs.msdn.com/b/johngossman/archive/2005/10/08/478683.aspx

我自己的文章:http://www.infoq.com/articles/View-Model-Definition


拥有一个“视图模型”只是包装一个模型并公开相同的属性列表是一种反模式。视图模型的工作是调用外部服务并公开这些服务返回的单个模型和模型集合。

原因:

  1. 如果直接更新模型,视图模型将不知道触发属性更改事件。这会导致 UI 不同步。
  2. 这严重限制了您在父视图模型和子视图模型之间发送消息的选项。
  3. 如果模型有自己的属性更改通知,#1 和 2 不是问题。相反,如果包装 VM 超出范围但模型没有超出范围,您必须担心内存泄漏。
  4. 如果您的模型很复杂,有很多子对象,那么您必须遍历整个树并创建第二个对象图来遮盖第一个对象图。这可能非常乏味且容易出错。
  5. 包装的集合特别难以处理。任何时候(UI 或后端)从集合中插入或删除项目时,影子集合都需要更新以匹配。这种代码真的很难正确。

这并不是说您永远不需要包装模型的视图模型。如果您的视图模型公开了与模型显着不同的属性并且不能仅用 IValueConverter 覆盖,那么包装视图模型是有意义的。

您可能需要包装视图模型的另一个原因是您的数据类由于某种原因不支持数据绑定。但即便如此,通常最好只创建一个普通的、可绑定的模型并从原始数据类中复制数据。

当然,您的视图模型将具有特定于 UI 的属性,例如当前选择了集合中的哪个项目。

【讨论】:

  • ViewModel 是调用存储库的原因,如果您使用的是存储库。如果你不是,那么它直接调用外部服务。视图模型的设计不会改变。
  • 作者“逃脱”是因为明白他在说什么。您宣传根本不需要视图模型,这是完全错误的。直接绑定到模型是例外情况,而不是正常情况。这里的问题是关于“做什么”。也许您重读了问题和我的答案。认为一些阅读可以帮助您了解 MVVM 的工作原理,但这似乎是徒劳的。特别是如果需求变得更复杂,则对良好的架构和设计的需求更高。但是,如果您不在乎这些事情,我为什么要打扰。
  • 如果您至少阅读过这篇文章,甚至可能您会找到中心部分。 “简单示例” 实际上不是生产代码。直接来自那篇文章:然而,实际上,只有一小部分应用程序 UI 可以直接数据绑定到模型也许您对现实世界的应用程序没有太多经验,但直接绑定到模型是我工作的公司和项目中的一个简单的禁止。但是,打开 MVVM 初学者指南是一个非常好的开始。我很感激。
  • 简单并不意味着“不生产”。简单意味着这是您的大部分代码应该遵循的模式。
  • @JonathanAllen 谢谢。你提出了一些非常好的观点。我同意 wrapper VM 是对代码的浪费,除了使项目结构更复杂之外,并没有给项目结构添加任何内容。虚拟机应该负责管理视图逻辑,而不是深入研究实际业务逻辑所在的模型。在此之上拥有封装虚拟机只会增加复杂性。
【解决方案2】:

INotifyPropertyChanged (INPC) 接口用于Binding

因此,一般情况下,您希望在 ViewModel 中实现它。

ViewModel 用于将Model 与您的View 解耦,因此您的Model 中无需包含INPC,因为您不希望Bindings 与您的Model 关联。

在大多数情况下,即使对于较小的属性,您仍然有一个非常小的ViewModel

如果您想为MVVM 打下坚实的基础,您可能会使用某种 MVVM 框架,例如caliburn.micro。使用它会给你一个ViewModelBase(或这里NotifyPropertyChangedBase),这样你就不必自己实现这些接口成员,只需使用NotifyOfPropertyChange(() => MyProperty),这样更容易,更不容易出错。

更新 由于那里似乎有许多 Windows 窗体开发人员,这里有一个很好的 这篇文章将更深入地了解 MVVM 的含义: MSDN Magazine on MVVM

我特别链接了有关数据模型的部分,即问题所在。

【讨论】:

  • 对我帮助最大的教程包括一个名为 MicroMVVM 的框架,它一直做得很好。它允许RaisePropertyChanged(() => Property,到目前为止我没有遇到任何问题。
  • 使用框架是一个好的开始。 Caliburn.micro 对我的帮助最大,因为它提供了 ActionMessages 和 EventAggregator,非常强大。
  • Caliburn.micro 与 MVVM Light 相比如何?我尝试了一段时间,并不太在意。
  • SO 要求我评论为什么我不赞成这个答案:因为乔纳森艾伦的回答实际上符合 MPP。
  • 我也不同意这个答案,出于同样的原因同意@JonathanAllen。如果视图已启动(由正在查看模型的视图模型支持),并且模型从视图以外的其他位置更改(可能接收到更改模型属性的网络命令),则视图将不同步,除非模型本身引发属性改变。 INotifyPropertyChanged 不是 WPF 的一部分(它在 System.ComponentModel 中)并且不知道绑定。实际上,WPF 绑定使用 INotifyPropertyChanged 来实现其绑定机制——但它们是不同的概念。
【解决方案3】:

绝对同意乔纳森·艾伦的观点。

如果您没有要添加到“视图模型”的内容(命令、影响演示的视图特定属性等),那么我肯定会在模型中实现 INotifyPropertyChanged 并直接公开它(如果可以的话 - '模型' 可能不是你的)。您不仅最终会重复大量样板代码,而且让两者保持同步绝对是一件痛苦的事情。

INotifyPropertyChanged 不是一个特定于视图的接口,它只做顾名思义——当属性改变时引发一个事件。 WinForms、WPF 和 Silverlight 恰好支持它用于 Binding - 我当然将它用于非展示目的!

【讨论】:

  • 你没有 ViewModel 也有 ViewModel。关键是您可以在需要时轻松添加此类行为,因为您首先做出了正确的设计决策。在你需要的时候实现一个好的设计是一团糟,而且它的成本远远超过一开始就拥有一个小的视图模型。最大的例外是你没有那些“要添加的东西”,没有那个。推荐的方法(由对设计有深入了解的人,而不是来自 infoq 的人)是在你的视图模型中公开模型。
  • 呃,没有。拥有 View-Model 的全部意义在于它可以让您将数据与对数据的外部操作分开。一旦您开始混合 ICommand(例如 Save)和普通属性(例如 First/Last Name),您就离开了模式。内部操作,如验证和计算属性(例如 FullName)应该在模型和单元测试中,但仅此而已。
  • 但是我为什么要打扰呢?如果我有一个显示“项目”的只读数据网格的视图,则将该网格绑定到 ObservableCollection<Item> 比必须盲目地创建一个 ItemViewModel 来包装它、同步代码要简单得多底层项目的更改以触发适当的PropertyChanged 事件,将底层集合同步到 ViewModel 集合的代码等。在这种情况下,直接绑定到模型会更简单——这甚至是 Prism 指南所说的:msdn.microsoft.com/en-us/library/…
  • (现在我重新阅读了您所说的内容,我不确定您是否同意我刚刚在上面写的内容 - 但很难理解!)
  • 对于它的价值,我同意你所说的@CharlesMager。
【解决方案4】:

MVVM 的创建者 JohnGossman 在this 博客文章(@Jonathan Allen 提到)中指出:

在简单的示例中,视图是直接绑定到模型的数据。 模型的部分内容通过单向数据简单地显示在视图中 捆绑。模型其他部分可以直接绑定编辑 控制数据的双向。例如,模型中的布尔值可以 将数据绑定到 CheckBox,或将字符串字段绑定到 TextBox。

但实际上,只有一小部分应用程序 UI 可以是数据 直接绑定到模型,特别是如果模型是预先存在的 应用程序开发人员没有的类或数据模式 控制。

我更喜欢遵循在应用扩展时仍然适用的做法。 如果“在实践中 [...],只有一小部分应用程序 UI 可以直接将数据绑定到模型”,这似乎不是一个好的做法,因为我不打算只解决“简单案例”或“应用程序 UI 的一小部分”。
对于“简单案例”,我什至不会一开始就使用 MVVM。

【讨论】:

    【解决方案5】:

    根据经验,您将绑定到的任何对象(即使您不需要双向绑定和属性更改通知)都必须实现INotifyPropertyChanged。这是因为没有这样做May cause memory leaks

    【讨论】:

      【解决方案6】:

      INotifyPropertyChanged 应该由视图使用的所有类型实现(当然,除非它只有常量值)。

      您是否将模型(不是视图模型)返回到视图?如果是,那么它应该实现 INotifyPropertyChanged。

      【讨论】:

      • 常量值很棘手。从 API 设计的角度来看,您不想公开该接口并混淆您的消费者。但是数据绑定架构中的缺陷意味着无论如何都可能想要这样做。有关更多信息,请参阅 @HighCore 的链接。
      【解决方案7】:

      虽然我通常支持实现 INPC 的模型,但复合视图模型中对 INPC 的调用是在它公开可绑定到视图的推断属性时。 IMO 因为 INPC 被嵌入到 System.dll 中,实现它的模型可能被认为是 POCO。对于集合,基于模型的 INPC 具有性能优势。在 64 位平台上,与 ObservableCollection 相比,包装 VM 的字节大小(加载 SOS 调试器扩展以获取实际大小)将具有 8 倍乘数。

      【讨论】:

        猜你喜欢
        • 2012-07-31
        • 2019-10-03
        • 2011-07-14
        • 2010-12-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多