MVVM中的模型是用来做什么的?

7
我已经阅读了几篇关于 MVVM 模式的文章、教程和博客帖子。然而有一件事我不明白。按照三个“层”:

  • 模型
  • 视图
  • 视图模型

据我所了解,MVVM 中模型包含“原始”数据,例如在 Student 类中包含姓名和地址。视图模型向视图公开表示模型数据的属性。

视图模型中属性的示例

public string Name {
 get { return model.Name; }
 set { model.Name = value; }
}

Example for the model

private string name;

public string Name {
 get { return name; }
 set { name = value; }
}

这可能听起来有点愚蠢,但这不是在创造冗余吗?为什么我必须在模型和视图模型中都保留名称?为什么不能完全在视图模型中处理名称?

1
顺便说一句,这是一个非常好的问题:清晰、简洁并且包含示例代码。 - JDB
5个回答

5
在这样一个简单的例子中,答案是肯定的(它是不合理的冗余)。但是,假设页面将包含不止一个Model对象。您可能具有页面状态以及必须跟踪所有其他Model对象。这是在ViewModel中完成的。
例如,您可以在状态栏中显示有关已登录用户的附加信息,以及运行以检测文本文件更改的服务。
您还可以拥有用于编辑Student对象的表单。如果您打算验证这些更改,那么在修改得到验证之前,您不希望直接编辑Student对象。在这种情况下,ViewModel可以充当临时存储位置。

关于上述内容的说明:在模型中进行验证并不罕见,但即使如此,在编辑表单的过程中,您可能仍希望用户能够输入无效值。例如,如果您的模型不允许在字段中出现零长度值,则仍然希望用户能够删除该值,转到另一个字段(例如复制它),然后返回该字段并完成编辑(粘贴)。如果您直接与模型绑定,则您的验证逻辑可能无法像您希望的那样处理这种“中间”、“尚未完成”的状态。例如,您可能不想在用户完成并点击“保存”之前就向其显示验证错误。

您还可能在ViewModel中拥有命令对象来处理按钮点击等操作。这些将是特定于领域的对象,在模型中将无用。

当您需要过滤或以某种方式暂时“修改”模型对象以在屏幕上获得有用信息时,ViewModel也很有用。例如,您可能希望显示系统中所有用户的列表以及实时列表中排名前十的表现者(每10秒更新一次)。或者您可能希望显示报告列表以及显示整体使用率的图表等。过滤、排序和自定义数据将在ViewModel中进行。

另一方面,模型通常尽可能纯净。理想情况下,您只希望有POCOs,这些POCOs通常正好对应您的持久存储(数据库等)。如果您的持久存储具有FirstName和LastName字段,则模型也将具有这些字段。仅在ViewModel中,您才会将它们组合在一起以获取Name字段(根据视图的需要,可以是“First Last”或“Last, First”)。例如:
namespace Model
{
    public class Student
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public class Class
    {
        public string Name { get; set; }
        public float Score { get; set; }
    }
}

namespace ViewModel
{
    public class EditStudentRecordViewModel
    {
        private Model.Student _student;
        private IEnumerable<Model.Class> _studentClasses;

        /* Bind your View to these fields: */
        public string FullName
        {
            return _student.LastName + ", " + _student.FirstName;
        }
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public IEnumerable<Model.Class> PassingClasses
        {
            get
            {
                return _studentClasses.Where( c => c.Score >= 78 );
            }
        }

        public IEnumerable<Model.Class> FailingClasses
        {
            get
            {
                return _studentClasses.Where( c => c.Score < 78 );
            }
        }

        public void Save()
        {
            List<string> l_validationErrors = new List<string>();
            if ( string.IsNullOrEmpty( this.FirstName ) )
                l_validationErrors.Add( "First Name must not be empty." );
            if ( string.IsNullOrEmpty( this.LastName ) )
                l_validationErrors.Add( "Last Name must not be empty." );

            if ( l_validationErrors.Any() )
                return;

            _student.FirstName = this.FirstName;
            _student.LastName = this.LastName;
            Model.Utilities.SaveStudent( _student );
        }
    }
}

5

模型是包含您的业务逻辑的对象图。

这是您保持行为(验证、计算等)的地方。

ViewModel 是建模 UI 及其交互的东西。

它们是不同的,存在的原因也不同 - 模式的重点是将显示逻辑分离到 VVM(视图和 ViewModel),并完全分离您的业务逻辑。


4
视图模型是用于跟踪与视图相关而不是必要的模型属性的地方。
假设您有一个名为“Person”的模型,那么您可以创建一个名为“PersonViewModel”的视图模型,它看起来像这样:
public class PersonViewModel
{
    public Person Person { get; set; }
}

(注意,您可能不想直接公开模型,但这是另一件事)

现在假设您在视图中有一个按钮,用于保存Person实例。为了提供更好的用户体验(UX),您希望仅在模型实际更改时启用该按钮。因此,您在Person类上实现INotifyPropertyChanged接口

public class Person : INotifyPropertyChanged
{
    ...

现在,您可以从您的 Person 中公开一个 HasUnsavedChanges 属性,保存按钮上的 Enabled 属性将绑定到该属性,但是这种逻辑与人无关。
这就是视图模型的作用。您可以在视图模型上定义此 特定于视图的属性,如下所示:
public class PersonViewModel
{
    public Person Person { get; set; }

    public bool HasUnsavedChanges { get; set; }
}

然后,您的视图模型将订阅INotifyPropertyChanged接口的PropertyChanged事件,并在视图模型上切换HasUnsavedChanges属性。
然后,如果绑定设置正确,则保存按钮将在模型发生任何更改时启用/禁用,但是您的模型没有将其与视图相关联的逻辑。
请注意,您还必须在视图模型上实现INotifyPropertyChanged,以便您的视图能够捕获绑定到的视图模型发生更改的情况。
再次强调,重点是充当桥梁,包含不属于模型的模型属性和视图属性的逻辑组合。

1
我认为你不应该这样做。Person似乎是一个领域/业务对象,它不应该有任何UI特定的逻辑。在我看来,INotifyPropertyChanged应该仅在ViewModel层上使用。 - IronHide

1

我一直认为模型是应用程序的“构建块”。它们通常是自包含类,具有一些属性和可能仅针对其自身属性进行一些基本验证或逻辑。

另一方面,视图模型是我的实际应用程序类,在构建和运行应用程序时使用“构建块”(模型)。它们执行高级验证、处理命令、处理事件、任何类型的业务逻辑等操作。

需要注意的是,您不必像在示例代码中那样在ViewModel中公开Model的属性。这是“MVVM纯粹主义者”的方法,因为它完全将您的模型层与视图层分离,但是将整个模型公开给视图也是完全可以接受的。由于其简单性和缺乏代码重复,这是我在大多数小项目中使用的方法。

public MyModel CurrentModel
{
    get { return _model; }
    set 
    {
        if (_model != value)
        {
            _model = value;
            RaisePropertyChanged("CurrentModel");
        }
    }
}

然而,如果只需要在视图中使用模型的少数属性,或者项目足够大以至于我想要完全分离层,则会像您在示例代码中所示那样通过ViewModel将我的模型属性暴露给视图。


1

MVVM模式中的Model与MVP或Model2 MVC中的相同。它是受主题变化影响最小的MVC风格模式之一。

Model层包含存储库、工作单元、领域/模型对象、数据映射器、服务和其他结构。所有这些结合起来创建了Model层,其中包含特定应用程序的所有领域业务逻辑。

Model不是任何单个实例。任何告诉你Model是单个实例的人都是在胡说八道。

MVVM的特定用例是当您无法修改模型层或视图实例或两者时。

P.S. 但是,如果您按照ASP.NET MVC文档使用ViewModel实例,则实际上并未使用MVVM。这只是具有不同名称的Model2 MVC(其中“viewmodels”实际上是视图,“views”是模板)的Rails-like架构的营销策略失误。


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