在MVVM中,每个ViewModel是否只与一个Model耦合?

40
在MVVM实现中,每个ViewModel是否只与一个Model耦合?
我正在尝试在项目中实现MVVM模式,但我发现有时View可能需要来自多个Models的信息。
例如,对于UserProfileView,其UserProfileViewModel可能需要来自UserAccountModelUserProfileSettingsModelUserPostsDataModel等的信息。
但是,在我阅读的大多数关于MVVM的文章中,ViewModel仅通过依赖注入包含一个Model。因此,构造函数仅接受一个Model。
ViewModel必须从多个Models获取信息时,该如何工作?或者MVVM中是否会出现这种情况?
PS:我没有使用Prism或Unity Framework。我正在尝试将类似的模式应用到我正在工作的项目中,该项目不使用Prism或Unity。这就是为什么我需要确切了解这些事情的工作方式的原因。

你为什么认为ViewModel不能绑定到不同的数据源? - Akku
视图只通过依赖注入包含一个模型。因此,构造函数仅接受一个模型。 - Carven
请重新阅读我的评论:是什么让你认为 ViewModel(而不是 View)不能绑定到其他不同的模型(而不是 ViewModels)? - Akku
是的,我指的是ViewModel,而不是View。打错字了,对此感到抱歉。 - Carven
ViewModel是View的模型,因此它仅与View直接相关。不要试图遵循规则,在模式和实践中没有严格的规则。 - Alex Burtsev
请忘记以下内容:ViewModel类仅通过其构造函数接受一个Model - 这是废话,请删掉这个想法 :) - blindmeis
7个回答

50

在我对MVVM模式的理解中,唯一实际的要求是View从ViewModel的属性(可能通过绑定机制)获取其所有数据。 ViewModel是专门为该视图设计的类,并承担根据需要填充自身的责任。您可以将其视为ActiveRecord用于视图。

因此,重要的不是您在ViewModel内部获取其属性应显示的数据的方式。您可以通过查询某些服务、读取一个或多个业务实体模型、即时生成它,或同时进行所有这些操作来获取它。需要组合所有这些元素以创建一个功能性视图是完全正常的。

就像在任何演示模式中一样,重点只是将显示屏幕上的数据的过程与获取该数据的过程分开。这样,您可以单独测试每个过程的各个部分。

编辑:这里是一个小但完整的依赖关系流程示例

// Model/service layer

public class MyModelA
{
  public string GetSomeData()
  {
    return "Some Data";
  }
}

public class MyModelB
{
  public string GetOtherData()
  {
    return "Other Data";
  }
}

// Presentation layer

public class MyViewModel
{
  readonly MyModelA modelA;
  readonly MyModelB modelB;

  public MyViewModel(MyModelA modelA, MyModelB modelB)
  {
    this.modelA = modelA;
    this.modelB = modelB;
  }

  public string TextBox1Value { get; set; } 

  public string TextBox2Value { get; set; }

  public void Load()
  {
    // These need not necessarily be populated this way. 
    // You could load an entity and have your properties read data directly from it.
    this.TextBox1Value = modelA.GetSomeData();
    this.TextBox2Value = modelB.GetOtherData();
    // raise INotifyPropertyChanged events here
  }
}

public class MyView
{
  readonly MyViewModel vm;

  public MyView(MyViewModel vm)
  {
    this.vm = vm;
    // bind to vm here
  }
}

// Application layer

public class Program
{
  public void Run()
  {
    var mA = new MyModelA();
    var mB = new MyModelB();
    var vm = new MyViewModel(mA, mB);
    var view = new MyView(vm);
    vm.Load();
    // show view here
  }
}

谢谢!这个例子真的帮助我很多,让我更清楚地理解了这个概念。+1 :) - Carven
1
这个代码可以正常工作,但是当 ModelA 和 ModelB 类中的数据发生变化时,如何通知 MyViewModel 类?如何将 MyViewModel 类与 ModelA 和 ModelB 类的变量绑定? - I.K.
通常情况下,您的模型只有在UI告诉它更改时才会更改。您可能会在某个地方点击保存按钮,然后可以使用该按钮刷新数据。但是,有时这并不容易实现。如果您的模型通过从外部服务获取数据自行更改,或者如果它被程序的其他部分修改,则最简单的方法是在模型/业务层中公开一个事件,并在ViewModel中侦听该事件。 - Alex J
当窗口关闭时,请确保取消订阅,否则窗口将绑定到模型的生命周期。最好的方法是在ViewModel上实现IDisposable,并在关闭时释放它,或者更好的是,让你的依赖注入容器处理它。 - Alex J

14
您可以在视图模型中使用多个模型。视图模型的目的是将业务/数据层(即模型)抽象出来。
然而,使用多个模型通常意味着视图过于庞大。您可能需要将其拆分为用户控件(它们有自己的视图模型)。

取决于你使用的是什么。任何IoC都可以注入多个模型。Caliburn.Micro(我最喜欢的MVVM框架)也是如此。 - jgauffin
嗯...我不明白IoC如何能够在ViewModel中只有一个单一参数的情况下注入多个模型到ViewModel中?我正在查看类似于http://i.imgur.com/ZKEDz.png的结构。 - Carven
@IliaJerebtsov 我从http://waf.codeplex.com/wikipage?title=Model-View-ViewModel%20Pattern获取了这个图表。因为我目前没有使用任何IoC框架,而是自己实现该模式,所以我需要了解它的工作原理。根据我所读的,ViewModel类只通过其构造函数接收一个Model。如果其他东西可以将多个Models注入到ViewModel中,我想知道如何做到?那么ViewModel会维护一个Models列表吗? - Carven
1
@xEnOn:我猜你所说的“Model”是指一种对象,它可以让你从程序的其他部分获取信息(例如:领域模型服务)。并没有限制你只能依赖其中一个,而且在你的ViewModel类中有多个构造函数参数也是完全可以的。你可以将这些引用存储在ViewModel中以备后用,例如使用只读字段。 - Alex J
@xEnOn:请查看我的答案,其中有我所说的代码示例。 - Alex J
显示剩余4条评论

4
一个 ViewModel 包含“视图逻辑”——所以你想要在视图中展示的所有内容都经由 ViewModel 传递。如果你想要显示来自不同“模型”的数据,则你的 ViewModel 将对其进行聚合,视图可以绑定到这些数据。

MVVM 的主要目的是单元测试。这意味着可以在没有 UI 的情况下轻松测试视图逻辑。

编辑:你为什么认为:

ViewModel 在其构造函数中只有一个 View 参数

编辑2:

MVVM 有两种主要的工作方式,第一种是“视图优先”,第二种是“ ViewModel 优先”。当然,你可以混合使用两种方法,并选择最适合你需求的方法。


2

ViewModel(视图模型)可能并且在很多情况下会使用多个Models(模型)。它本身是您视图的“Model”(模型)。

考虑一个用户输入其个人信息(包括地址)的个人资料屏幕。如果地址存储在“addresses”表中,其他信息存储在“profile”表中,则ViewModel使用Profile和Address models来创建一个统一的ViewModel。

正如jgauffin在他的答案中提到的,很多时候您可以使用用户控件来实现一对一的关系,但是尝试100%这样做也可能引入不必要的复杂性。


2
我会确保您理解视图、视图模型和其他所有模型类之间的区别。ViewModel是填充数据以便将其绑定到视图的模型对象。它只存在于为视图提供数据,这使得ViewModel对象可单元测试,并将整个业务逻辑与视图分离。因此,您可以完全开发业务逻辑而不使用视图本身,并且可以仅构建或使用另一个视图并绑定到ViewModel对象的属性来替换视图。例如,如果视图中有很多空文本字段,则可以将文本字段的内容绑定到ViewModel的不同属性上。
通常应该只有一个ViewModel。但是,如果它太复杂,您可以使用绑定对象的子属性,如绑定到ViewModel.SubClass.Property(子属性)所述。
ViewModel可以从许多不同的源(业务对象、数据库等)获取返回给视图的数据。

1
通常每个模型都有一个ViewModel。这些ViewModel包含处理模型数据的逻辑。另一方面,每个视图也有自己的视图模型。因此,这意味着:
class ModelA 
{
    bool TestValue{get;set;}
}
class ViewModelA<ModelA>
{
    ValueViewModel<bool> TestValue{get; private set;}

    public ViewModelA(ModelA model) 
    {
        base.Model = model;
        this.Initialize();
    }
}

class ModelB 
{
    string Username;
}
class ViewModelB<ModelB>
{
    ValueViewModel<string> Username{get; private set;}

    public ViewModelB(ModelB model) 
    {
        base.Model = model;
        this.Initialize();
    }
}

这些是封装模型的ViewModels。视图有自己的ViewModels:

public ViewModelForExactlyOneView
{
    public ViewModelA{get;set;}
    public ViewModelB{get;set;}
}

回答你的问题,ViewModel1 指的是 ViewModelA 和 ViewModelB。因此,视图可以从 ViewModel1.ViewModelA.TestValue 获取数据。

1
哎? 你可能需要重新写一下,让它更清晰一些。对我来说,它听起来像是一个 View 应该有多个 ViewModels。 - Andrew Grothe
1
“每个视图都有自己的ViewModel”这句话难以理解吗?很抱歉我的语言表达能力不好,但我完全同意你的观点。一个视图与一个ViewModel相对应。但是这个ViewModel本身可以引用其他几个ViewModel。 - PVitt
@agrothe:我试图澄清ViewModel的使用。对不起,我的语言能力不好... - PVitt
更让人困惑的是“回答你的问题,ViewModel1 是指 ViewModelA 和 ViewModelB”的那一句话。代码有助于澄清你想要表达的意思。 - Andrew Grothe

-4

在你的视图中使用用户模型即可

public partial class User : Login
{
    public string Password { get; set; }

    public List<Customer> customer { get; set; }
}

在这个模型中,另一个模型登录被继承,并且客户模型也在此模型中使用。


3
永远不要在视图中使用模型! MVVM 正是关于这个设计缺陷,并希望通过 ViewModel 概念来解决它。 - PVitt

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