在MVVM模式中,ViewModel或Model应该实现INotifyPropertyChanged吗?

177
我曾经学习的大多数 MVVM 示例都让 Model 实现了 INotifyPropertyChanged 接口,但在 Josh Smith 的 CommandSink 示例中ViewModel 实现了 INotifyPropertyChanged

我仍在认知上理解 MVVM 概念,所以我不知道:

  • 你必须在 ViewModel 中加入 INotifyPropertyChanged 才能让 CommandSink 生效
  • 这只是一种与规范不符的现象,没有太大关系
  • 你应该总是让 Model 实现 INotifyPropertyChanged,这只是一个错误,如果将其从代码示例开发成应用程序就会得到纠正

在你参与的 MVVM 项目中,别人有哪些经验呢?


4
如果你要实现 INPC,不妨试试 https://github.com/Fody/PropertyChanged - 它会让你节省数周的打字时间。 - CAD bloke
17个回答

158

我强烈反对认为模型不应实现INotifyPropertyChanged的概念。这个接口不仅适用于UI!它只是通知有变化发生了。确实,WPF大量使用它来识别变化,但这并不意味着它是一个UI接口。

我会将它比作以下评论:“轮胎是汽车配件”。当然,没错,但自行车、公共汽车等也使用它。总之,不要把这个接口看成是UI相关的东西。

话虽如此,并不意味着我认为模型一定要提供通知。事实上,原则上,除非必要,否则模型不应该实现这个接口。在大多数情况下,如果没有服务器数据推送到客户端应用程序,则模型可以过时。但是,如果监听金融市场数据,那么我认为模型为什么不能实现这个接口呢?例如,如果我有非UI逻辑,例如服务,当它收到给定值的出价或询问价格时,它会发出警报(例如通过电子邮件)或下订单,这可能是一种合适的解决方案。

然而,有不同的方法可以实现事情,但我总是会主张简单性并避免冗余。

什么更好?在集合上定义事件还是在视图模型上定义属性更改并将其传播到模型,或者让视图本身更新模型(通过视图模型)?

最重要的是,当您看到有人声称“你不能这样做或那样做”时,这表明他们不知道自己在说什么。

这实际上取决于您的情况,事实上MVVM是一个存在很多问题的框架,我还没有看到普遍实现MVVM的方法。

我希望我有更多时间来解释MVVM的许多变体以及一些常见问题的解决方案-大多数由其他开发人员提供,但我想我将不得不再找时间来讲解了。


8
这么想吧,如果你作为开发人员使用一个带有模型的.dll文件,你显然不会重新编写它们以支持INotifyPropertyChanged。 - Lee Treveil
2
非常同意你的观点。就像我一样,你可能也会高兴地发现官方的MVVM文档 http://msdn.microsoft.com/en-us/library/gg405484%28v=pandp.40%29.aspx(Model部分)与我们的看法一致。 :-) - Noldorin
2
INotifyPropertyChangedSystem.ComponentModel命名空间的一部分,该命名空间用于处理组件和控件的运行时和设计时行为。请不要在模型中使用INotifyPropertyChanged,只在视图模型中使用它。有关详细信息,请参阅文档:https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel - Igor Popov
2
旧帖子,我知道,但是每当我使用MVVM开始新项目时,我经常回到它。最近我开始更加严格地执行单一职责原则。一个模型应该只有一个职责,即成为一个模型。一旦你在模型中添加INotifyPropertyChanged,它就不再遵循单一职责原则了。ViewModel存在的整个原因是让模型成为模型,让模型具有单一职责。 - Rhyous
3
即使模型被包装,模型仍然有能力响应模型中的其他变化而自我改变。例如,假设更改员工对象的薪水也会导致员工的税收等级发生变化。那么像ViewModel这样的包装器如何知道TaxBracket已经发生了变化呢?@Rhyous - redcurry
显示剩余5条评论

116

我认为恰恰相反,我总是把我的INotifyPropertyChanged放在我的ViewModel上 - 你真的不想用一个相当特定于WPF的功能来污染你的模型,这些东西应该放在ViewModel中。

我相信其他人可能会有不同的看法,但这是我的工作方式。


88
如果模型中的属性发生了变化,你需要想办法将其传递到视图模型中。老实说,我现在正在处理这个难题。 - Roger Lipscombe
5
Prism代码中的EventAggregator是INotifyPropertyChanged在模型上的一个很好的替代品,具有自定义属性更改事件类型。该项目中的事件代码支持在后台线程和UI线程之间转发事件,这有时可能会成为一个问题。 - Steve Mitcham
56
INotifyPropertyChanged 不仅仅适用于 WPF,它也存在于 System.ComponentModel 命名空间中。我曾在 WinForms 应用程序中使用过它。此外,INotifyPropertyChanged 从 .Net 2.0 开始就已经存在了,而 WPF 只是从 3.0 开始出现。 - benPearce
43
我支持将INotifyPropertyChanged接口应用于MODEL和VIEWMODEL。我认为这样做没有任何不妥之处。这是一种优雅的方式,可以告知VIEWMODEL背景中发生了对VIEWMODEL影响的变化,就像它被用来告知VIEW有关VIEWMODEL更改的情况一样。请注意,这个翻译仅供参考,可能会因为语言习惯、上下文等原因而有所不同。 - ScottCher
7
为了通知ViewModel某个Model的属性已经改变,似乎INotifyPropertyChanged作为"ViewModel可以连接到的事件"就足够了。为什么不使用它呢? - skybluecodeflier
显示剩余6条评论

31

那第一句话很好地概括了其他答案的观点,我认为应该点赞! - j00hi

13

我认为MVVM的命名非常不恰当,将ViewModel称为ViewModel会导致许多人忽略一个良好设计架构的重要特征,即DataController控制数据而不管是谁试图触及它。

如果您将View-Model看作更像DataController,并实现一种体系结构,使得您的DataController是唯一触及数据的项目,那么您永远不会直接触摸数据,而总是使用DataController。 DataController对UI很有用,但不一定仅适用于UI。它适用于业务层、UI层等等...

DataModel -------- DataController ------ View
                  /
Business --------/

你最终得到了这样一个模型。即使是业务方也只需使用ViewModel接触数据。然后你的问题就迎刃而解了。


4
如果您的数据只在DataController更改时发生更改,那就太好了。如果数据来自数据库或其他可以提供另一种更改途径的数据存储,您可能需要一种通知VIEWMODEL(模式中的DataController)和VIEW发生更改的方式。您可以使用DataController进行轮询或从某些外部进程推送到您的DataModel,并允许您的DataModel向DataController发送更改通知。 - ScottCher
5
你说得完全正确。设计模式非常高级。大多数情况下,设计模式会引导你做正确的事情,但有时它们会让你走错路。你不应该因为某些操作不在你的设计模式之内而回避去做正确的事情。 - Rhyous
您还需要将其推送到DataController,因为它控制数据模型,并告诉它进行更新。 - Rhyous
此外,MVVM中的模型应该根据UI的需要保持特定(例如DTO)。因此,任何数据库或复杂的业务逻辑都应该在不同的层中发生,然后通过视图模型提供粗略数据。 - Code Name Jack

10

我同意Paulo的回答,将INotifyPropertyChanged实现在模型中完全可以接受,甚至是微软建议的 -

通常,模型实现使其易于绑定到视图的功能。这通常意味着它支持通过INotifyPropertyChangedINotifyCollectionChanged接口进行属性和集合更改通知。表示对象集合的模型类通常派生自ObservableCollection<T>类,该类提供了对INotifyCollectionChanged接口的实现。

尽管实现这种类型的实现取决于你,但请记住 -

如果您的模型类没有实现所需的接口怎么办?

有时您需要使用不实现INotifyPropertyChangedINotifyCollectionChangedIDataErrorInfoINotifyDataErrorInfo接口的模型对象。在这些情况下,视图模型可能需要包装模型对象并向视图公开所需的属性。这些属性的值将由模型对象直接提供。视图模型将实现它公开的所需接口,以便视图可以轻松地将它们绑定到数据上。

摘自 - http://msdn.microsoft.com/en-us/library/gg405484(PandP.40).aspx

我曾经在一些项目中工作,我们没有在模型中实现INotifyPropertyChanged,因此我们遇到了很多问题;在VM中需要不必要的属性复制,并且同时我们还需要更新下层对象(使用更新后的值)才能将其传递给BL/DL。

特别是如果您需要使用模型对象的集合(例如可编辑的网格或列表)或复杂模型,您将会面临问题;模型对象不会自动更新,您将不得不在VM中管理所有这些内容来更新它们。


9
取决于您如何实现您的模型。我的公司使用类似于Lhotka的CSLA对象的业务对象,并在整个业务模型中广泛使用INotifyPropertyChanged。我们的验证引擎严重依赖于通过此机制通知属性更改,并且运行非常良好。显然,如果您使用的不是业务对象等其他实现,其中对更改的通知并不像操作那样关键,则可能有其他方法来检测业务模型中的更改。我们还有View Models,在需要时从Model传播更改,但是View Models本身正在监听底层Model的更改。

3
您想知道如何将模型(Model)的OnPropertyChanged传播到视图模型(ViewModel)的OnPropertyChanged。如果ViewModel具有与Model不同的属性名称,则需要一种名称映射的方法,对吗? - Martin Konicek
这不是什么真正复杂的东西,我们只是简单地转发事件。我想如果名称不同,那么可以使用查找表。如果更改不是一对一映射,则可以简单地钩住事件,然后在处理程序中触发必要的事件。 - Steve Mitcham

5

我认为这完全取决于使用情况。

当您有一个具有大量属性的简单模型时,可以让它实现INPC。我所说的简单是指这个模型看起来很像POCO

如果您的模型更复杂,并且存在于交互式模型领域 - 模型引用模型,订阅其他模型的事件 - 则将模型事件实现为INPC是一场噩梦。

把自己放在某个模型实体的位置上,该模型实体必须与其他模型协作。您有各种事件要订阅。所有这些事件都实现为INPC。想象一下您拥有的那些事件处理程序。一个巨大的if语句和/或switch语句级联。

INPC的另一个问题。您应该设计您的应用程序依赖于抽象,而不是实现。通常使用接口来完成此操作。

让我们看一下相同抽象的两个不同实现:

public class ConnectionStateChangedEventArgs : EventArgs
{
    public bool IsConnected {get;set;}
}

interface IConnectionManagerINPC : INotifyPropertyChanged
{
    string Name {get;}
    int ConnectionsLimit {get;}
    /*

    A few more properties

    */
    bool IsConnected {get;}
}

interface IConnectionManager
{
    string Name {get;}
    int ConnectionsLimit {get;}
    /*

    A few more properties

    */
    event EventHandler<ConnectionStateChangedEventArgs> ConnectionStateChanged;
    bool IsConnected {get;}
}

现在看看它们两个。IConnectionManagerINPC告诉你什么?它的一些属性可能会改变,但你不知道哪些属性会改变。事实上,设计只是IsConnected会改变,因为其余属性是只读的。
相反,IConnectionManager的意图很明确:“我可以告诉你我的IsConnected属性的值可能会发生改变”。

3

但有时(如在此演示中链接文本),模型是服务,它向应用程序提供一些在线数据,然后您需要使用事件实现通知新数据已到达或数据已更改...


3
如果你希望遵循MV-VM模式,那么答案就非常明显了。在MVVM模式中,视图封装UI和任何UI逻辑,视图模型封装演示逻辑和状态,而模型封装业务逻辑和数据。视图通过数据绑定、命令和更改通知事件与视图模型交互。视图模型查询、观察并协调对模型的更新,根据需要将数据转换、验证和聚合以便在视图中显示。请参见:http://msdn.microsoft.com/en-us/library/gg405484(v=PandP.40).aspx

5
这句话的意思可以有不同的解释。我认为您应该加入自己的解释,以便回答更加清晰明了 :-) - Søren Boisen
@John D:那篇文章只是提供了一种MVVM的解释和实现方式,它并没有定义MVVM。 - akjoshi
此外,如果您阅读完整的文章,它会像这样定义Model类:“通常,模型实现了使其易于绑定到视图的功能。这通常意味着它通过INotifyPropertyChanged和INotifyCollectionChanged接口支持属性和集合更改通知。表示对象集合的模型类通常派生自ObservableCollection<T>类,该类提供了INotifyCollectionChanged接口的实现。” - akjoshi

3

我认为应该放在你的ViewModel中。它不是Model的一部分,因为Model与UI无关。Model应该是“除业务无关以外的所有内容”。


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