在 MVVM 中,ViewModel 或 Model 是不是应该实现 INotifyPropertyChanged?

Posted

技术标签:

【中文标题】在 MVVM 中,ViewModel 或 Model 是不是应该实现 INotifyPropertyChanged?【英文标题】:In MVVM should the ViewModel or Model implement INotifyPropertyChanged?在 MVVM 中,ViewModel 或 Model 是否应该实现 INotifyPropertyChanged? 【发布时间】:2010-10-20 19:24:59 【问题描述】:

我处理过的大多数 MVVM 示例都有 Model 实现 INotifyPropertyChanged,但在 Josh Smith's CommandSink example ViewModel 实现 INotifyPropertyChanged

我仍在认知上将 MVVM 概念放在一起,所以我不知道是否:

您必须将 INotifyPropertyChanged 放入 ViewModel 才能使 CommandSink 工作 这只是规范的偏差,并不重要 您应该始终让模型实现 INotifyPropertyChanged,这只是一个错误,如果将其从代码示例开发为应用程序,则会得到纠正

其他人在您参与的 MVVM 项目上有何经验?

【问题讨论】:

如果您确实实现了 INPC,请尝试 github.com/Fody/PropertyChanged - 它可以节省您数周的打字时间。 【参考方案1】:

我想说的恰恰相反,我总是将我的 INotifyPropertyChanged 放在我的 ViewModel 上 - 你真的不想用像 INotifyPropertyChanged 这样的相当 WPF 特定的功能来污染你的模型,这些东西应该放在视图模型。

我相信其他人会不同意,但我就是这样工作的。

【讨论】:

如果模型中的属性发生变化,你会怎么做?您需要以某种方式将其放到视图模型中。诚实的问题,我现在正在处理这个难题。 Prism 代码中的 EventAggregator 是模型上 INotifyPropertyChanged 的​​一个很好的替代方案,具有自定义属性更改事件类型。该项目中的事件代码支持在后台线程和 UI 线程之间转发事件,这有时会成为问题。 INotifyProperyChanged 不是 WPF 特有的,它位于 System.ComponentModel 命名空间中,我在 WinForms 应用程序中使用过它,INotifyPropertyChanged 自 2.0 以来一直在 .Net 中,WPF 自 3.0 以来才出现跨度> 我很喜欢将 INotifyPropertyChanged 放在 MODEL 和 VIEWMODEL 中。我想不出不这样做的理由。它是一种优雅的方式来通知 VIEWMODEL 何时在您的 MODE 中发生了影响 VIEWMODEL 的背景变化,就像它用于通知 VIEW 并且 VIEWMODEL 发生了变化一样。 @Steve - 关于通知 ViewModel 模型的属性已更改,看起来 INotifyPropertyChanged 作为“viewmodel 可以挂钩的事件”工作得很好。为什么不使用它?【参考方案2】:

我会在您的 ViewModel 中说。它不是模型的一部分,因为模型与 UI 无关。模型应该是“除了业务无关的一切”

【讨论】:

【参考方案3】:

这取决于您实现模型的方式。我的公司使用类似于 Lhotka 的 CSLA 对象的业务对象,并在整个业务模型中广泛使用 INotifyPropertyChanged

我们的验证引擎在很大程度上依赖于通过这种机制获得属性更改的通知,并且它运行良好。显然,如果您使用的是业务对象以外的其他实现,而业务对象的更改通知对操作而言并不那么重要,那么您可能有其他方法来检测业务模型中的更改。

我们也有视图模型,可以根据需要从模型传播更改,但视图模型本身正在侦听基础模型更改。

【讨论】:

究竟如何将 Model 的 OnPropertyChanged 传播到 ViewModel 的 OnPropertyChanged?当 ViewModel 具有与 Model 不同的属性名称时,我遇到了问题 - 那时需要某种名称到名称的映射,对吗? 这并不复杂,我们只是转发事件。我想如果名称不同,则可以使用查找表。如果更改不是一对一的映射,那么您可以简单地挂钩事件,然后在处理程序中触发必要的事件。【参考方案4】:

在 M-V-VM 中,ViewModel 总是(Model not always)实现INotifyPropertyChanged

查看来自http://blogs.msdn.com/llobo/archive/2009/05/01/download-m-v-vm-project-template-toolkit.aspx 的 M-V-VM 项目模板/工具包。 它使用DelegateCommand 进行命令,它应该是您 M-V-VM 项目的一个很好的起始模板。

【讨论】:

第一句话很好地总结了其他答案的选项,imo。 => 点赞!【参考方案5】:

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

【讨论】:

【参考方案6】:

我强烈不同意模型不应实现INotifyPropertyChanged 的概念。此界面不是特定于 UI 的!它只是通知更改。实际上,WPF 大量使用它来识别更改,但这并不意味着它是一个 UI 界面。 我会将其与以下评论进行比较:“轮胎是汽车配件”。当然可以,但自行车、公共汽车等也使用它。 总之,不要把那个界面当作 UI 的东西。

话虽如此,但这并不一定意味着我相信模型应该提供通知。 事实上,根据经验,模型不应该实现这个接口,除非有必要。在大多数情况下,没有服务器数据推送到客户端应用程序,模型可能是陈旧的。但是如果听金融市场数据,那我不明白为什么模型不能实现接口。例如,如果我有非 UI 逻辑,例如当它收到给定值的买价或卖价时它会发出警报(例如通过电子邮件)或下订单的服务,该怎么办?这可能是一个干净的解决方案。

然而,实现目标有不同的方式,但我总是主张简单并避免冗余。

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

当你看到有人声称“你不能做这个或那个”时,这表明他们不知道他们在说什么。

这真的取决于你的情况,事实上 MVVM 是一个有很多问题的框架,我还没有看到一个通用的 MVVM 实现。

我希望我有更多的时间来解释 MVVM 的多种风格以及常见问题的一些解决方案 - 主要由其他开发人员提供,但我想我将不得不再做一次。

【讨论】:

这样想。如果您作为开发人员使用包含模型的 .dll,那么您肯定不会重写它们以支持 INotifyPropertyChanged。 非常同意你的观点。像我一样,您可能也会很高兴地发现官方 MVVM 文档 msdn.microsoft.com/en-us/library/gg405484%28v=pandp.40%29.aspx>(模型部分)与我们一致。 :-) “然而,实现目标的方式各不相同,但我总是主张简单并避免冗余。”非常重要。 INotifyPropertyChangedSystem.ComponentModel 命名空间的一部分,该命名空间用于“组件和控件的运行时和设计时行为”。 请勿在模型中使用 INotifyPropertyChanged,仅在 ViewModel 中。文档链接:docs.microsoft.com/en-us/dotnet/api/system.componentmodel 老帖子,我知道,但是在使用 MVVM 开始一个新项目时,我经常会回到它。我最近开始更加严格地执行单一职责原则。一个模型就是有一个责任。成为模特。一旦将 INotifyPropertyChanged 添加到模型中,它就不再遵循单一责任原则。 ViewModel 存在的全部原因是让模型成为模型,让模型承担单一职责。【参考方案7】:

我认为 MVVM 的命名非常糟糕,并且将 ViewModel 称为 ViewModel 会导致许多人错过精心设计的架构的一个重要特性,即无论谁试图触碰它都可以控制数据的 DataController。

如果您将 View-Model 更多地视为 DataController 并实现一个架构,其中您的 DataController 是唯一接触数据的项目,那么您永远不会直接接触数据,而是始终使用 DataController。 DataController 对 UI 很有用,但不一定只对 UI 有用。用于业务层、UI层等...

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

您最终会得到这样的模型。即使是业务也应该只使用 ViewModel 来接触数据。然后你的难题就消失了。

【讨论】:

如果您的数据仅在 DataController 更改时更改,那就太好了。如果数据来自数据库或可以提供另一种更改途径的其他数据存储,您可能需要有一种方法在发生这种情况时通知 VIEWMODEL(您的模式中的 DataController)和 VIEW。您可以使用 DataController 进行轮询或从某个外部进程推送到您的 DataModel,并允许您的 DataModel 将更改通知发送到您的 DataController。 你完全正确。设计模式非常高级。大多数时候,设计模式会引导你做正确的事情,但时不时地会让你走错路。您永远不应该避免做正确的事情,因为它超出了您的设计模式。 您还可以推送到 DataController,因为它控制和数据模型,并告诉它更新。 此外,MVVM 中的模型应保持特定于 UI 的需要(例如 DTO)。所以任何数据库或复杂的业务逻辑都应该发生在不同的层,然后应该通过视图模型提供粗略的数据。【参考方案8】:

通常 ViewModel 将实现INotifyPropertyChanged。模型可以是任何东西(xml 文件、数据库甚至对象)。模型用于将数据提供给视图模型,然后传播到视图。

see here

【讨论】:

emm.. 没有。模型不能是 xml 文件或数据库。并且模型不用于提供数据。否则它应该被称为不是“模型”而是“数据”..?模型用于描述数据。很自我解释,不是吗? :) 如果你有更好的答案,请分享!我们都在这里分享知识,而不是竞争【参考方案9】:

假设您的视图中对象的引用发生了变化。您将如何通知所有要更新的属性以显示正确的值?在您看来,为所有对象的属性调用 OnPropertyChanged 在我看来是垃圾。

所以我要做的是让对象本身在属性中的值更改时通知任何人,在我看来,我使用诸如Object.Property1Object.Property2 等绑定。这样,如果我只想更改当前在我的视图中维护的对象,我只需执行OnPropertyChanged("Object")

为了避免在加载对象期间收到数百个通知,我有一个私有布尔指示器,我在加载期间将其设置为 true,从对象的 OnPropertyChanged 检查它并且什么都不做。

【讨论】:

【参考方案10】:

我在模型中使用INotifyPropertyChange 接口。实际上,模型属性更改只能由 UI 或外部客户端触发。

我注意到了几个优点和缺点:

优势

Notifier 在商业模式中

    根据域驱动,它是正确的。它应该决定何时加注,何时不加注。

缺点

模型具有属性(数量、费率、佣金、总运费)。 TotalFrieght 是使用数量、费率、佣金变化来计算的。

    在从 db 加载值时,调用总运费计算 3 次(数量、费率、佣金)。应该是一次。

    如果是rate,qty在业务层分配,再次调用notifier。

    应该有一个选项来禁用它,可能在基类中。但是,开发人员可能会忘记这样做。

【讨论】:

由于您列出的所有缺点,我们刚刚决定在我们相对较大的 WPF 项目中在模型中实现 INPC 是一个错误的决定。他们应该只处理持久层。所有其他事情,如验证、更改通知和计算属性都应该在 ViewModel 中处理。现在我清楚地明白,在 ViewModel 中重复模型属性并不总是违反 DRY 原则。【参考方案11】:

恕我直言,我认为 viewmodel 实现了INotifyPropertyChange,并且模型可以在不同的“级别”上使用通知。

例如 使用一些文档服务和文档对象,您有一个 documentChanged 事件,视图模型会监听该事件以清除和重建视图。 在编辑视图模型中,您对文档的属性进行了属性更改以支持视图。如果该服务在保存时对文档做了很多工作(更新更改日期、最后一个用户等),您很容易得到 Ipropertychanged 事件的过载,并且只需一个 documentchanged 就足够了。

但是,如果您在模型中使用 INotifyPropertyChange,我认为在您的视图模型中传递它而不是直接在您的视图中订阅它是一种很好的做法。在这种情况下,当模型中的事件发生变化时,您只需更改视图模型并且视图保持不变。

【讨论】:

【参考方案12】:

只需在您的视图模型中使用INotifyPropertyChange,而不是在模型中,

该模型通常使用IDataErrorInfo 来处理验证错误,因此只需保留在您的 ViewModel 中,您就可以在 MVVM 道路上前进。

【讨论】:

具有多个属性的模型呢?您在 VM 中重复代码。【参考方案13】:

我认为这完全取决于用例。

当您有一个带有大量属性的简单模型时,您可以让它实现 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 属性的值可能会改变”。

【讨论】:

【参考方案14】:

如果你想坚持 MV-VM,我认为答案很明确。

见: http://msdn.microsoft.com/en-us/library/gg405484(v=PandP.40).aspx

在 MVVM 模式中,视图封装 UI 和任何 UI 逻辑,视图模型封装表示逻辑和状态,模型封装业务逻辑和数据。

"视图通过数据绑定与视图模型交互, 命令和更改通知事件。视图模型查询, 观察并协调模型的更新,转换, 根据需要验证和聚合数据以在视图中显示。 "

【讨论】:

这句话可以解释。我认为您应该添加您的解释,以使您的答案清楚:-) @John D:那篇文章只是给出了对 MVVM 的一种解释和一种实现方式,它没有定义 MVVM。 此外,如果您阅读完整的文章,它会像这样定义 Model 类:“通常,模型实现了可以轻松绑定到视图的工具。这通常意味着它支持属性和集合通过 INotifyPropertyChanged 和 INotifyCollectionChanged 接口更改通知。表示对象集合的模型类通常派生自 ObservableCollection 类,该类提供 INotifyCollectionChanged 接口的实现。"【参考方案15】:

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

通常情况下,模型实现的功能可以让您轻松 绑定到视图。这通常意味着它支持属性和 通过INotifyPropertyChangedINotifyCollectionChanged 接口。模型类代表 对象的集合通常源自 ObservableCollection&lt;T&gt; 类,它提供了 INotifyCollectionChanged接口。

虽然由您决定是否需要这种类型的实现,但请记住 -

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

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

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

我曾参与过一些我们没有在模型中实现INotifyPropertyChanged 的项目,因此我们遇到了很多问题;在 VM 中需要不必要的属性重复,同时我们必须在将底层对象(使用更新的值)传递给 BL/DL 之前更新它们。

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

【讨论】:

【参考方案16】:

绑定到我的视图的所有属性都在我的 ViewModel 中。因此他们应该实现 INotifyPropertyChanged 接口。因此视图获得所有更改。

[使用 MVVM Light 工具包,我让它们继承自 ViewModelBase。]

模型包含业务逻辑,但与视图无关。因此不需要 INotifyPropertyChanged 接口。

【讨论】:

【参考方案17】:

如果模型在 ViewModel 中明确公开,则可以在模型中实现 INPC。但一般来说,封装模型的 ViewModel 是他自己的类,以降低模型复杂度(这对绑定应该没有用处)。在这种情况下,INPC 应该在 ViewModel 中实现。

【讨论】:

以上是关于在 MVVM 中,ViewModel 或 Model 是不是应该实现 INotifyPropertyChanged?的主要内容,如果未能解决你的问题,请参考以下文章

WPF 在 MVVM 模式下实现窗口后台代码与ViewModel交互

MVVM:GUI 和 ViewModel 之间的真正分离

MVVM 并将可选值传递给 viewModel

Jetpack MVVM七宗罪 之三 :在 onViewCreated 中请求数据

Android - MVVM中ViewModel状态的最佳实践?

MVVM 架构中点击事件的位置