MVVM:VM 对象应该直接公开 M 对象,还是仅通过委托给 M 的 getter 的 getter?

Posted

技术标签:

【中文标题】MVVM:VM 对象应该直接公开 M 对象,还是仅通过委托给 M 的 getter 的 getter?【英文标题】:MVVM: Should a VM object expose an M object directly, or only through getters delegating to M's getters? 【发布时间】:2010-11-10 00:23:58 【问题描述】:

最好的解释方式是这样:

这是模型

public class Person 

    public int age;
    public string name;

这是视图模型

public class PersonVM
    

我的问题是: 虚拟机应该将人暴露给数据模板还是用他自己的属性封装模型属性?

【问题讨论】:

在这些简单的场景中,拥有视图模型的好处为零。如果您说想要实现撤消功能等,那么该逻辑将进入 VM,您将包装模型属性。 【参考方案1】:

没有关于这个问题的普遍共识。例如,这是 Ward Bell here 提出的关于 MVVM 的开放式问题之一:

是否允许 VM 提供 V an 展开的 M 对象(例如,原始 员工) ?或者必须是 M 对象的 属性(如果它甚至被允许 有属性!)被暴露 完全通过一个表面 虚拟机包装器?

不直接在 VM 中公开模型的主要优点是:

您可以将其用作“类固醇转换器”,以方便的方式为视图格式化模型值

您可以注入其他与用户界面相关的功能,例如data validation messages、undo redo、..

缺点是:

您必须复制大量代码才能在视图模型中公开所有模型属性。

如果您将视图控件绑定到 viewmodels 属性,您将从 viewmodel 发送 propertyChanged 事件。但是,如果模型属性从不同于 viewmodel 设置器的其他来源更改会发生什么?然后它必须通知视图模型,所以你以 2 个 OnPropertyChanged 结束,一个在模型中,一个在视图模型中……相当复杂!

所以对我来说正确的答案是:这取决于您的要求。

【讨论】:

有一个简单的解决方案,让视图模型成为代理,包含您想要添加的所有功能 这是一个很好的例子,展示了“包装”模型然后在顶部注入更多功能的好时机。 @ChenKinnrot - 实际上,“缺点”“使视图模型成为代理”所需的编码的描述,因为当任何属性发生变化时,视图模型必须通知视图。将代码“注入”到属性中的库可以提供帮助,例如 Fody/PropertyChanged。如果修改了 Model 对象的属性,那么痛苦就来了,那么必须告知该 M 对象的任何 VM(S)哪个 M 属性发生了更改,以便它们可以触发其相应的 OnPropertyChanged,以告知数据绑定的 V。【参考方案2】:

Robert McCarter 在 MSDN 第 25 卷中提出了一个有趣的解决方案。

http://msdn.microsoft.com/en-us/magazine/ff798279.aspx

他使用动态视图模型在模型之上提供一个层,同时避免代理所有模型属性。

如果您的问题空间不需要高性能(动态确实会影响性能),这是一个很好的解决方案。 View 不需要知道有关 Model 的任何信息,但 View Model 不必代理“按原样”提供的属性。可以随时将属性添加到 View Model 以包装 Model 属性,而无需修改 View 或 Model。阅读文章了解更多详情。

【讨论】:

或者使用两全其美... 定义一个动态基础视图模型类和一个非动态基础视图模型。对高性能视图模型使用非动态,对其他一切使用动态基类。 记住 Silverlight 不支持 DLR :'( 我也讨厌代理属性。 @Shimmy:这并不奇怪。 Silverlight 仍然缺少 WPF 的许多优点。【参考方案3】:

拥有任何模型的 ViewModel 可能比这更糟糕。如果你有一个模型的层次结构,甚至是一个简单的集合怎么办? 在这种情况下,您必须遍历所有模型并为每个模型构建一个 ViewModel 实例,并且还必须注册通知更改事件或其他事件。恕我直言,这完全是疯狂和不合理的。正如 DaniCE 所说,您最终会遇到大量代码和头疼的问题。

【讨论】:

我同意你的看法,现在怎么样了? 将现有模型树包装到 VM 中是一个相对简单的过程。创建一个实用程序来执行此操作,或者更好地将其构建到您自己的基础 VM 类中。是的,设置虚拟机并将它们与视图耦合会增加一些复杂性,但这是值得的。结果是可视化树的每个级别的智能代码元素。无需再搜索可视化树来寻找处理操作的人员。 在没有视觉命中测试的情况下,我从来没有找到一种处理单个 ListBoxItems 复杂操作的好方法......直到 MVVM。一旦您构建了 VM 层次结构并为每个 VM 提供了正确的引用,ListBoxItems 就拥有了执行自己的复杂操作所需的一切。 如果您不将模型与视图分开,为什么需要 viemmodel?您最终可能会将模型设置为数据上下文。 @IgnaciosolerGarcia - 确实可以。请注意,在实践中,这意味着将 ViewModel 中的所有内容添加到您的模型中。例如。从 System.Component.INotifyPropertyChanged 继承并在每个公共属性的设置器上触发 OnPropertyChanged。 (或者从任何 MVVM 库中的适当 VM 基类继承,并根据 MVVM 库的需要标记这些属性。)所以对我来说,实际上问题变成了 “你需要一个 模型?"(总是需要一个视图模型)。【参考方案4】:

视图模型应该声明它自己的属性并在视图中隐藏模型的细节。这为您提供了最大的灵活性,并有助于防止视图模型类型问题泄漏到模型类中。通常,您的视图模型类通过委托封装模型。例如,

class PersonModel 
    public string Name  get; set; 


class PersonViewModel 
    private PersonModel Person  get; set;
    public string Name  get  return this.Person.Name;  
    public bool IsSelected  get; set;  // example of state exposed by view model

    public PersonViewModel(PersonModel person) 
        this.Person = person;
    

请记住:模型不应该知道任何关于正在使用它的视图模型的信息,并且视图模型也不应该知道任何关于正在使用它的视图的信息。视图应该对潜伏在后台的模型一无所知。因此,将模型封装在视图模型中的属性后面。

【讨论】:

PersonViewModel 类有一个带有 PersonModel 参数的公共构造函数。所以视图必须知道模型才能实例化视图模型……不是mvvm。构造函数应该是内部的,或者只接受像字符串这样的通用参数来实例化模型。 假设您更改模型中的值(通过 UI)。因此,模型随后也会更改一些其他值(因为它们依赖于它)。然后你会触发 OnPropertyChangedHandler 并让 VM 更新它自己的属性吗?这是我所做的并且效果很好,但我是新手,不确定它是否是不好的做法。 回复上述评论。这篇文章似乎完全实现了我所做的。 msdn.microsoft.com/en-us/magazine/ff798279.aspx

以上是关于MVVM:VM 对象应该直接公开 M 对象,还是仅通过委托给 M 的 getter 的 getter?的主要内容,如果未能解决你的问题,请参考以下文章

域实体应该作为接口还是作为普通对象公开?

02-Vue单向数据绑定与双向数据绑定

02-Vue单向数据绑定与双向数据绑定

02-Vue单向数据绑定与双向数据绑定

02-Vue单向数据绑定与双向数据绑定

对 mvvm 架构的理解