【问题标题】:Does MVVM violate DRY?MVVM 是否违反 DRY?
【发布时间】:2010-11-01 02:55:50
【问题描述】:

似乎我制作的 ViewModel 看起来与其他类很像,而且它们似乎需要大量代码重复,例如在我当前的项目中:

  • SmartFormModel,表示要填写的数据表单,具有以下属性:
    • IdCode
    • 标题
    • 说明
    • SmartFormFields 集合
  • SmartFormControlView 查看
  • SmartFormControlViewModel ViewModel
    • IdCode
    • 标题
    • 说明
    • SmartFormFields 集合

所以我的ViewModel与我的Model基本相同,只是具有用于与View绑定的所有OnPropertyChanged功能。

似乎在我重构和扩展这一点时,我对模型所做的每一个小改动,我都必须进行 对 ViewModel 的镜像更改

这似乎违反了模式的基本规则不要重复自己

是我错误地实现了 MVVM 模式,还是只是 MVVM 的固有特性,即 Model 和 ViewModel 之间总是存在一对一的重复?

【问题讨论】:

  • 我经常想知道同样的事情......除了在我的情况下,我确实选择在我的模型上实现更改通知(对我来说更有意义 - - 它们是存储数据的地方,因此它们会知道数据何时发生变化)。这使得重复更加突出。
  • 我的方法是模型应该能够在任何环境中使用,因此如果您想在 ASP.NET MVC 应用程序中使用它们,您不需要更改通知。所以我认为模型上的更改通知会使它们锁定在 WPF 环境中,对吗?
  • 为什么不在 SmartFormControlViewModel 中有一个 SmartForm 类的实例,而不是复制字段?
  • 不,更改通知不会将您锁定在 WPF 中。在其他环境中它们只是不必要的。 (INotifyPropertyChanged 在 System.dll 中,因此它是核心 .NET Framework 的一部分,而不是任何特定于 WPF 的库的一部分。)但我确实喜欢你的推理——这是我没有想到的。

标签: wpf design-patterns mvvm


【解决方案1】:

Eric Evans 在他的“领域驱动设计”一书中提到,模型重构不应该太难,并且概念更改不应该跨越太多模块,否则,重构模型变得令人望而却步,所以,如果你问我,在 ViewModel 中“复制”了 Model 类型肯定会使 Model 重构变得困难。

Eric 提到,应该更加重视模型的凝聚力和隔离性,而不是基于技术问题(数据库访问、POCOS、表示)的层划分的整洁性。最重要的问题是域模型是业务域的良好表示,因此域模型最重要的是位于单个隔离层中,而不是跨越多个模块,以便它易于更新(重构)。

考虑到刚才所说的,我会在 ViewModel 中使用相同的 Model 对象,如果我想降低对 Model 对象的“访问”级别,那么我会“传递”对接口的引用由模型对象实现。例如:

    // The primary Model classes
    public partial class OrderItem {
        public int Id { get; }
        public int Quantity { get; set; }
        public Product Item { get; set; }
        public int Total { get; set; }
        public void ApplyDiscount(Coupon coupon) {
            // implementation here
        }
    }

    public partial class Product {
        public int Id { get; }
        public string Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }    
    }

    // The shared views of those model classes
    public partial class OrderItem : IOrderItemDTO {
        public IProductDTO Item { 
            get { 
                return this.product; 
            } 
        }
    }

    public partial class Product : IProductDTO {
    }


   // Separate interfaces...
   // You enforce the rules about how the model can be
   // used in the View-ViewModel, without having to rewrite
   // all the setters and getters.
    public interface IOrderItemDTO {
        int Id { get; }
        int Quantity { get; set; }
        IProductDTO Item { get; }
        int Total { get; }
    }

    public interface IProductDTO {
        string Name { get; }
        string Description { get; }
        decimal Price { get; }
    }

    // The viewmodel...
    public class OrderItemViewModel {
        IOrderItemDTO Model { get; set; }
    }

【讨论】:

    【解决方案2】:

    其他人就 MVC/MVVM 模式的组件角色提供了很好的 cmets。无论您选择哪种模式,我想提供一个基本的观察来解释重复性。

    通常在您的数据层、业务层和 UI 层之间会有某种重复。毕竟,通常您必须向最终用户(UI)显示每个属性,model它的行为(业务层)和persist值(数据层)。

    正如其他人所指出的,该属性在每一层上的处理方式可能略有不同,这解释了一些重复的基本需求。

    在处理足够大的系统(或与合适的团队一起处理小型项目)时,我倾向于在 UML 中对此类信息进行建模,并使用代码生成(通常与部分类相结合)来处理重复方面。举个简单的例子,Last Name 属性可能有一个要求(在我的 UML 模型中),它将数据限制为 50 个字符。我可以生成代码以在我的 UI 层中强制执行该限制(例如,通过物理限制输入),在我的业务层中生成代码以重新检查该限制(“永远不要相信 UI”),如果数据太长,可能会抛出异常,并生成我的持久层(例如 NVARCHAR(50) 列、适当的 ORM 映射文件等)。

    2012 年更新

    Microsoft 的 Data Annotations 及其在 UI 层(例如 ASP.Net MVC)和数据层 (Entity Framework) 中的支持对于实现我之前为其生成代码的许多问题大有帮助。

    【讨论】:

      【解决方案3】:

      我认为是的,vanilla MVVM 确实违反了 DRY。但是我已经开始the PDX library,我认为它可以在很多情况下解决这个问题。我写了this post,我相信它解决了这个问题。

      基本上,我的目标(其中之一)是拥有不用担心 UI 通知的 Viewmodel。 PDX 项目仍处于起步阶段,但如果您正在阅读此问题,您可能会发现它很有用,如果您有任何反馈,我将不胜感激。

      【讨论】:

        【解决方案4】:

        这里似乎遗漏了一件并且您的简单示例没有公开的事实是,您的视图通常会聚合包含在多个域模型类型中的数据。在这种情况下,您的 ViewModel 将包含对许多域模型(不同类型)的引用,从而聚合了特定 View 可能希望公开的一堆相关数据。

        【讨论】:

          【解决方案5】:

          一个简单的解决方案是拥有一个公开模型的抽象 ViewModel(VM) 基类。您可以在有意义的情况下选择此 VM。

          public abstract class ViewModelBase<T>
          {
              public T Model { get; set; }
          }
          

          如果您的模型实现了 INotifyPropertyChanged,您的视图将获得该事件。这样做的作用是让您的视图访问模型中的每个属性,这有时不是您想要的。

          你也可以使用这样的属性初始化器(我个人存储在代码 sn-ps 中):

          public abstract class SampleViewModel
          {
              public int MyProperty
              {
                  get { return Model.MyProperty; }
                  set
                  {
                      Model.MyProperty = value;
                      OnPropertyChanged("MyProperty");
                  }
              }
          }
          

          在大多数情况下,您查看的将是对您的 VM 进行更改的人,当它进行更改时,绑定到该属性的任何控件都会被告知发生了某些事情。

          希望对您有所帮助。

          【讨论】:

            【解决方案6】:

            我个人认为它不违反 DRY,因为模型和视图模型(我更喜欢术语演示者)不指向相同的信息。例如,您的 VM 和 M 都具有 Title 属性,但您的 VM 的 Title 属性也可以包含验证,而您的模型的 Title 属性可以假设有效性。

            虽然 VM 确实可能包含模型的所有属性,但也有可能具有验证(例如,标题必须为非空白)、数据依赖性、可绑定的 UI 特定属性(图标、颜色、画笔等),它们不属于视图。

            基本上所有的 UI 模式都具有类似的“重复”,即您所说的方式:即级联修改。尝试在 MVC 中更改模型而不更改控制器。

            话虽如此,MVVM(或任何旨在分离 UI、逻辑和状态的 UI 模式)对于简单的案例(例如您的示例)可能过于繁琐。当逻辑变得比状态传递多一点时,分离控制器/呈现器/视图模型的价值就会降低。

            在您的特定情况下,如果您的虚拟机确实没有任何逻辑、验证或 UI 特定属性,并且您的模型不必持久化、序列化或向后兼容现有结构(或在 VM 中添加这样做的逻辑很简单),我强烈考虑将 M 和 VM 结合起来,以避免创建其唯一目的是获取/设置底层模型属性的属性。

            【讨论】:

            • 我永远不会假设模型级别的有效性。抽象出模型的一个主要好处是您可以用其他东西(不同的 UI、Web 服务)替换 UI 层并获得代码重用。如果将模型验证委托给原始 UI,则模型将无法正确重用。
            • @Eric J:回复晚了,但你的观点很好。在大多数 MVVM 示例中以及 WPF/SL 绑定基础结构本身所建议的模型中,模型在行为上很轻(例如 Fowler 的贫血域模型),将验证推迟到数据绑定时间,例如虚拟机层。我很好奇您关于数据绑定和限制其对域模型的入侵同时依赖域模型进行验证的想法和方法。
            • @EricJ.,相反,必须强制执行域模型(聚合)不变量以提供模型一致性。如果模型不强制执行其自身的一致性,那么它的目的是什么?
            • @SergeyBrunov:这不是我说的吗?
            • @EricJ.,请原谅。我没听懂你上一条消息的最后一句话。这完全一样。
            【解决方案7】:

            我只知道 MVC 并且在 MVC 中包含 GUI 的模型类是一些错误。 SmartForm 似乎是一个表单,这意味着它不是一个模型。我不知道你要编程什么,但我给你一个模型的例子:

            拿日历。你可以问班上今天是几号,几月几号,每个月有多少天,... 虽然它没有图形表示。视图(CalenderViewMonth 或您想要的任何东西)在屏幕上打印一个月。它知道日历并询问他在不同的单元格中写什么。

            基本上 - 您可能在建模/理解 MVVM(它是 MVC 的现代 .NET 变体)方面有问题。


            编辑:

            我刚刚在 Wikipedia 上查找了 MVVM。模型就像 MVC 中的模型。也像 MVC 中的视图一样查看 - 仅图形表示。 ViewModel 是通用视图和专用模型之间的粘合剂。某种适配器。不应该违反 DRY。

            【讨论】:

            • SmartForm 只是一个数据持有者,它决定 UI 表单的外观,在 MVVM 中,如果可以的话,视图只会连接到模型,但模型没有任何“绑定魔法” (OnPropertyChanged,ObservableCollection),因此您必须创建一个与模型基本相同但具有这些绑定功能的 ViewModel,ViewModel 可能不必为特定视图实现模型的所有属性和功能,但在我的经验它经常这样做,所以 VM = M + 绑定魔法。
            • 我同意,这里的重复来自一个无用的层(SmartForm)。
            【解决方案8】:

            这是一个有趣的评论...确实,经常需要修改 ViewModel 以反映模型中的变化。

            如果它可以是自动的,那就太好了...实际上我认为通过在 ViewModel 中实现 ICustomTypeDescriptor 是可能的:GetProperties 将通过反射返回模型的所有属性。但是我不确定这是否有意义,因为模型可能根本不包含属性:它可能是方法、字段或任何东西,并不是模型中的所有内容都对 ViewModel 有用。

            【讨论】:

            • 目前我正在通过 sn-ps 和代码生成来处理它,我怀疑/希望新版本的 WPF 和 Visual Studio 将会对 MVVM 提供更多支持,以便可以创建 ViewModel也许使用实体框架等。也许这在 EF 4.0 中已经成为可能?
            • "也许这在 EF 4.0 中已经可以实现?" :嗯,我没有看到任何关于 .NET 4.0 中的 MVVM 的具体内容......也许微软会以工具包的形式提供一些东西,就像他们最初为 ASP.NET AJAX 和 WPF 工具包(已部分包含在 WPF 4) 中
            猜你喜欢
            • 2014-02-15
            • 2020-08-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-07-31
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多