【问题标题】:The model in MVVM: business object or something else?MVVM 中的模型:业务对象还是其他?
【发布时间】:2010-12-24 23:30:05
【问题描述】:

我正在尝试掌握 MVVM,因此我阅读了很多文章 - 大部分都关注 View -> ViewModel 关系,并且对于什么是什么达成了普遍共识。 ViewModel -> Model 关系以及构成 Model 的内容较少受到关注,并且存在分歧。我很困惑,需要一些帮助。例如,this article 将模型描述为一个业务对象,而this article 描述一个管理业务对象的类。这些是正确的还是其他的?

【问题讨论】:

    标签: silverlight mvvm model


    【解决方案1】:

    模型增加的价值是它与 ViewModel 和 View 的解耦。想想如果你必须在 ViewModel 中构建和维护业务逻辑,你会有很多重复的代码。

    例如 - 如果您有一个带有 GearBoxView(CockpitView 中的控件)、CarViewModel 和 CarModel 的汽车游戏 - 从 CarViewModel 中抽象出 CarModel 中的内容的优点是 CarModel 可以在 WorldViewModel 和任何其他 ViewModel。 CarModel 可以与其他模型(GearsModel、WheelModel 等)建立关系。

    您的问题专门询问了有关使用模型作为业务对象或管理业务对象的问题:我的回答是它可以做到这两点 - 并且应该对两者负责。

    举个例子

    public class WorldModel //Is a business object (aka game object)
    {
        private List<CarModel> _cars;
        public List<CarModel> Cars
        {
            get //Here's some management of other business objects
            {
                //hits NetworkIO in a multiplayer game
                if(_cars == null)
                {
                  _cars = myExternalDataSource.GetCarsInMyGame();
                }
                return _cars;
            }
        }
        public Level CurrentRaceCourse { get; set; }
        public CourseTime TimeElapsed { get; set; }
    }
    

    【讨论】:

    • 我不同意 ViewModel 和 Model 并解耦。事实上,我会说它们恰恰相反。
    • 尽管您在回答中同意它......通过解耦清楚我的意思是视图模型和视图中没有模型的依赖关系(即:您将数据绑定到虚拟机和M,但模型不应该指向相反的方向)。
    【解决方案2】:

    我认为你在正确的轨道上。 “模型”在很多情况下是模糊的因为它与其他人不同,这是正确的。

    对于,我的业务从我的 WCF 服务返回的对象我认为是我的模型。因此,我的项目没有那种漂亮的文件结构和三位一体的命名空间: *.Models、*.ViewModels 和 *.Views。我个人考虑从业务逻辑返回的对象或任何类似性质的“模型”

    有些人们倾向于将业务对象和业务逻辑混为一谈,并将其称为“模型”,但我觉得这有点令人困惑,因为我描绘了一个模型比我的业务逻辑更静态,但它是语义

    因此,当您查看 MVVM 项目的示例并且看不到任何非常清晰的“模型”时,这只是因为人们对待它们的方式不同。除非应用程序非常独立,否则我实际上会非常怀疑具有实际 *.Model 命名空间的应用程序,老实说。

    另一件很棒的事情是,很多时候您已经对这些类型的业务对象进行了投资,我认为很多人看到“MVVM”并立即假设他们需要开始定义“M”,甚至尽管他们已经拥有的一切都很好。

    Model 和 ViewModel 之间的混淆也很常见。基本上如果我需要数据和行为的组合,我知道我需要一个 ViewModel。例如,我不希望 INotifyPropertyChanged 在模型上实现,但我希望在 ViewModel 上实现。

    【讨论】:

    • 我虽然你有一个很好的答案,直到最后。我真的不明白为什么您不希望 INotifyPropertyChanged 在模型上实现?我经常在模型上实现这一点,您还希望如何通知多个视图核心业务数据的更改? (例如,您的模型可能是一个 Employee 类。如果 Employee.Name 为特定实例更改,您希望 View/ViewModel 收到该更改的通知)。
    • 因为这是一种行为。模型不需要特定于视图的行为。这不是模型的责任。例如,如果您创建 WCF 服务引用,则这些对象没有实现 INotifyPropertyChanged。如果您需要这种级别的行为,您可以将该类型实现为 ViewModel。
    • 这并不是说这不是一个常见的问题。流行的观点是您的模型应该与 WPF 几乎没有关系(这样它就可以在非 WPF 上下文中重用等)。关于这个主题的SO有很多线程:1)stackoverflow.com/questions/839118/… 2)stackoverflow.com/questions/772214/… 3)stackoverflow.com/questions/857820/…
    【解决方案3】:

    有很多不同的实现和解释。

    然而,在我看来,ViewModel 的价值来自于协调。

    模型代表业务数据。它封装了标量信息,而不是过程。

    View显然是模型的呈现。

    ViewModel 是一个协调器。在我看来,视图模型的工作是在视图和模型之间进行协调。它不应该包含业务逻辑,但实际上是与业务服务的接口。

    例如,如果您有一个小部件列表视图,并且小部件是从服务中获取的,那么我会假设:

    模型List&lt;Widget&gt; View 是绑定到 ViewModel 属性 Widgets 的 ListBox ViewModel 公开了 Widgets 属性。它还有一个 IWidgetService 引用,它可以调用它来获取这些小部件。

    在这种情况下,视图与业务对象协调,因此视图不必了解它的任何信息。模型应该不知道视图模型、视图和其他所有东西……它们应该独立于它们的使用方式而存在。 IWidgetService 将使用某些依赖注入容器源绑定到视图模型,使用 Unity 的构造函数注入或使用 MEF 的导入等。

    希望这是有道理的……不要超载您的视图模型。将其视为理解业务对象和模型但不了解视图或如何业务流程执行的协调器。

    【讨论】:

      【解决方案4】:

      从其他答案可以看出,ViewModel 和 Model 之间的关系有些模糊。请注意,没有什么能阻止您将 ViewModel 和 Model 放在同一个类中,当您在特定领域的要求足够简单时,也许这就是您所需要的!

      如何构建 ViewModel 和 Model 之间的分离将在很大程度上取决于需要它的项目或软件的需求、您的截止日期有多苛刻以及您对拥有结构良好且可维护的代码库的关心程度。

      分离 ViewModel 和 Model 只是一种结构化代码的方式。即使在这种模式下,也有许多不同的方式来构建代码!因此,您将听到不同程序员宣扬的不同方法也就不足为奇了。主要的是,分离有助于简化代码的独立部分并使其可重用。当您拥有清晰分离的业务数据、业务逻辑和表示逻辑时,您可以轻松混合、匹配和重用您的视图、逻辑和数据来创建新的 UI。分离和简化的代码通常也更易于理解、测试、调试和维护。

      显然不是每个人都会同意这个答案。我认为这是问题固有的模糊性的一部分。一般来说,您需要考虑并权衡分离 ViewModel 和 Model 的优势与成本,并且知道决定 ViewModel 中的内容和 Model 中的内容并不总是一项简单的任务。制定一些您或您的组织将遵循的基本规则可能会有所帮助,然后在您了解哪种级别的分离最适合您的问题域时改进您的规则。

      我认为值得一提的是,在编写 Windows 窗体时,我曾经使用与 MVVM 类似的方法,而 WPF 对此有更直接的支持(以数据绑定和命令的形式)这一事实确实让我很开心。

      【讨论】:

      • 我同意你的大部分帖子,但有一些不一致之处。第一个是你的代码应该是可重用和独立的,但如果有意义的话,你应该混合你的 ViewModels 和 Models。如果您的模型打算被重用(例如,在 WinForm 或 ASP.NET 应用程序中),那么混合 WPF 特定的行为(如 INotifyPropertyChanged)和您的模型将抑制这种能力。
      • 我想表达的主要观点之一是,您是否具有组合的 ViewModel/Model 或两层抽象,甚至更多抽象层取决于您的需求和要求。没有简单的方法来解释如何进行这种分离。它需要一些经验,一些关于如何编写结构良好的代码的理想,但也很大程度上取决于你必须花多少时间来处理它以及你的截止日期是什么等等。但是你可能有另一种意见,我想这完全是在您的情况下有效。
      • 顺便说一句,我在我的 Windows 窗体和 WPF 模型上都使用了 INotifyPropertyChanged,而且这些天我还在有意义的地方使用了 ObservableCollection(在 WPF 之前,我有自己的类似实现)。可能有比这更完美的代码分离级别,但完美带来了成本,我在我的数据模型中使用这些工具,因为我可以将 UI 直接绑定到有意义的模型。我同意这可能不是一个完美的关注点分离,但我必须在截止日期前编写代码并发现我的方法在实践中效果很好。
      【解决方案5】:

      我认为模型包含最小的业务实体单元。模型中的实体不仅在我的应用程序中的视图模型中使用,甚至在应用程序中使用。因此,一个模型提供了许多应用程序,并且对于那些使用 MVVM 的应用程序,提供了许多视图模型。

      视图模型是模型中实体的任意集合,这些实体组合在一起以服务于视图所需的任何内容。如果一个视图需要其中的 2 个和其中的 1 个,那么它的视图模型会从模型中提供它们。通常,每个视图有 1 个视图模型。

      所以模型就像杂货店。视图模型就像一个购物车。一个视图就像一个家庭。

      每个家庭都有独特的要求。每个家庭都有自己的购物车,可以从杂货店挑选家庭需要的东西。

      【讨论】:

        【解决方案6】:

        我的想法

        (“模型”)

        有一个模型。只是数据没有方法(除非适合平台一些-simple-getters/setters)。

        (“视图模型”)

        在我看来,视图模型的基本原理是:

        (1) 提供对 RAM 要求较低的字段备份副本,以便隐藏在其他视图后面的视图可以卸载和重新加载(以节省 RAM,直到它们从覆盖它们的视图后面重新出现)。显然,这是一个一般概念,可能对您的应用没有用处或不值得。

        (2) 在具有更复杂数据模型的应用程序中,将所有应用程序字段布局在一个视图模型中比创建一个与每个可能的数据变化字段对应的简化模型的工作更少,并且更易于维护,并且通常不会显着降低性能。

        如果这些都不适用,我认为使用视图模型是不合适的。

        如果视图模型合适,则视图模型与视图之间存在一对一的关系。

        可能需要注意/提醒/指出,对于许多 UI 工具包,如果完全相同的“字符串”对象被引用两次(在模型和视图模型中),那么字符串对象使用的内存可能很重要本身不是双重的,它只是多一点(足以存储对字符串的额外引用)。

        (“视图”)

        视图中唯一的代码应该是(必需的)显示/隐藏/重新排列/填充初始视图加载和(当用户滚动或单击显示/隐藏详细信息按钮等时)的控件显示/隐藏视图的一部分,并将任何更重要的事件传递给代码的“其余部分”。如果需要任何文本格式或绘图或类似内容,视图应该调用代码的“其余部分”来完成这项肮脏的工作。

        (重新审视“视图模型”)

        如果(...显示哪些视图的事实和...)视图字段的值是持久的,即在应用程序关闭/重新启动后仍然存在,则视图模型是模型的一部分 :--: 否则它不是。

        (重新审视“观点”)

        视图确保视图模型在适当的字段更改方面与视图同步,这可能非常同步(在文本字段中的每个字符更改时)或例如仅在初始表单填充时或者当用户点击某些“开始”按钮或请求关闭应用程序时。

        (“休息”)

        应用启动事件:从 SQL/network/files/whatever 填充模型。如果视图模型持久,则构造附加到视图模型的视图,否则创建初始视图模型并创建附加到它们的初始视图。

        在用户事务或应用关闭事件后提交:将模型发送到 SQL/networkl/files/whatever。

        允许用户(“有效地”)通过视图编辑视图模型(您是否应该在文本字段中字符的最微小变化时更新视图模型,或者仅在用户单击某些“开始”按钮时更新视图模型取决于在您正在编写的特定应用程序以及您正在使用的 UI 工具包中最简单的应用程序上)。

        在某些应用程序事件中:事件处理程序查看视图模型中的数据(来自用户的新数据),根据需要更新模型,根据需要创建/删除视图和视图模型,将模型/视图模型刷新为需要(以节省 RAM)。

        当必须显示新视图时:在创建视图模型后立即从模型中填充每个视图模型。然后创建附加到视图模型的视图。

        (相关问题:如果任何主要用于显示(非编辑)的数据集不应完全加载到 RAM 中怎么办?)

        对于由于 RAM 使用考虑而不应完全保存在 RAM 中的对象集,请创建一个抽象接口来访问有关对象总数的信息,并一次访问一个对象。

        接口及其“接口使用者”可能必须处理未知/估计和/或根据提供对象的 API 源而变化的对象数量。这个接口对于模型和视图模型可以是相同的。

        (相关问题:如果任何主要用于编辑的数据集不应完全加载到 RAM 中怎么办?)

        通过稍微不同的界面使用自定义分页系统。支持字段的修改位和对象的删除位 - 这些保留在对象中。将未使用的数据分页到磁盘。必要时使用加密。编辑完成后,对其进行迭代(一次加载页面 - 一页可以说 100 个对象)并写入所有数据或仅在事务或批处理中进行适当的更改。

        (MVVM的概念意义?)

        干净的平台无关方式允许和丢失视图中的数据更改,而不会破坏模型;并且只允许经过验证的数据进入模型,该模型仍然是数据的“主”净化版本。

        理解为什么从视图模型到模型的流程取决于用户输入的数据验证,而从模型到视图模型的相反方向的流程不是。

        分离是通过将了解所有这三个 (M/V/VM) 的代码放入一个核心对象中来实现的,该核心对象负责处理应用程序事件,包括高级别的启动和关闭。此代码必然引用单个字段和对象。如果不是这样,我认为无法轻松分离其他对象。


        针对您最初的问题,这是一个关于模型中字段更新的验证规则相互关系程度的问题。

        模型在它们可以存在的地方是扁平的,但引用子模型,直接用于一对一关系或通过数组或其他容器对象实现一对多关系。

        如果验证规则的复杂性使得仅根据字段正则表达式和数字范围列表验证成功的用户表单填写或传入消息(并根据相关参考对象的缓存或专门获取的副本检查任何对象和/或键)足以保证对业务对象的更新将是“完整的”,并且规则由应用程序作为事件处理程序的一部分进行测试,然后模型可以只具有简单的 getter 和 setter。

        应用程序可能(最好)直接在规则数量如此简单的事件处理程序中直接内联。

        在某些情况下,甚至将这些简单的规则放在模型的设置器中可能会更好,但是这种验证开销会在数据库加载时产生,除非您有额外的函数来设置而不进行验证。因此,对于更简单的数据模型,我倾向于将验证保留在应用程序事件处理程序中,以避免编写这些额外的设置器。

        但如果规则更复杂:

        (a) 为每个复杂的模型更改编写一个采用特殊对象的单个方法,该对象实际上是包含无数字段更改数据的常用业务对象的组合,并且该方法可能成功或失败,具体取决于规则的验证- 立面图案;

        (b) 首先创建模型或模型子集的“试运行”/假设/“试验”副本,一次设置一个属性,然后在副本上运行验证例程。如果验证成功,则将更改同化到主模型中,否则将丢弃数据并引发错误。

        在我看来,在分析每个单独的事务时首选简单的 getter/setter 方法,除非您的应用程序的绝大多数更新都很复杂,在这种情况下,可以始终使用外观模式。

        模型最终变得比具有(可能)简单 getter/setter 的一堆字段更复杂的另一种方式是,如果您开始“增强”类的 getter/setter(使用 O2R 映射器工具),或者添加额外的方面例如调用事务监控 API、安全 API(用于检查权限、用于日志记录等)、会计 API、预获取获取或设置所需的任何相关数据的方法,或获取或设置时的任何内容。有关该领域的说明,请参阅“面向方面的编程”。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-12-21
          • 2019-11-05
          • 2020-01-28
          • 1970-01-01
          • 2020-01-18
          相关资源
          最近更新 更多