C# MVVM:添加新的ViewModel(严格不公开的Model设计)

4

我一直在使用C#开发MVVM应用程序,但是在处理View消化的ViewModel集合时,经常遇到一些问题。具体而言,它们都与Model作为ViewModel的私有成员的问题有关。

其中一个例子是创建新的ViewModel(由View请求)。以下是示例Model和ViewModel类的前置说明(尽管您可能不需要这些来帮助我):

Private Class Model()
{
    public string Name { get; set; }
}

Public Class ViewModel()
{
    Private Model _Model;

    Public Void ViewModel(Model model)
    {
        _Model = model;
    }

    Public String Name
    { 
        get
        {
            return _Model.Name;
        }
        set
        {
            _Model.Name = value;
        }
    }
}

整个模型从未直接作为ViewModel的公共成员暴露。 MainWindowViewModel 处理 Model 的集合(私有,视图看不到这些)和 ViewModel 的集合(公共,供视图消化):
Public Class MainWindowViewModel
{
    Private List<Model> _NamesModel;
    Private ObservableCollection<ViewModel> _NamesViewModel;

    Public Void MainWindowViewModel()
    {
        //Lets pretend we have a service that returns a list of models
        _NamesModel = Service.Request();
        foreach(Model model in _NamesModel)
        {
            ViewModel viewmodel = new ViewModel(model);
            _NamesViewModel.Add(viewmodel);
        }
    }

    Public ObservableCollection<ViewModel> NamesViewModel
    {
        get
        {
            return _NamesViewModel;
        }
    }
}

现在这是前言,但我有一个问题。如何添加新的ViewModel?我的视图中的方法是否会创建一个新的ViewModel并填充它?作为一个纯粹主义者,我认为View不应该被允许创建或填充Model。我的ViewModel是否应该包含一个不接受任何参数(即没有基础模型)的构造函数,而是创建一个空白以填充?
这些问题在“纯”MVVM方法中一直存在。我不得不在我的ViewModel中创建一个公共方法(bool compare(Model model)),该方法将比较一个模型(准备删除等)与其内部模型。如果模型是公开的(破坏了纯度),那么像查找与模型连接的ViewModel这样的事情就容易多了。
2个回答

2
我可以理解一些这些问题。最近我编写了一个MVVM应用程序,类似的问题经常出现。其中一个技巧是要决定哪个类将负责模型实例 - 明确地做出决定。您想让它成为您的MainWindowViewModel还是NameViewModel? 你不希望在这两个类之间共享创建/删除模型的职责; 否则你会有很多后勤上的麻烦。
其次,即使是“纯粹”的MVVM方法也不表示您不能公开模型。您自己说过,这样做会节省很多麻烦:那就这么做吧。MVVM仅规定ViewModel没有View的知识/访问权限。有许多“官方”的MVVM示例甚至使用INotifyPropertyChanged接口来实现其模型,并直接绑定到模型上的属性。
个人而言,我认为我会将NameModel的控制权交给NameViewModel。这意味着您应该完全从MainWindowViewModel中删除NameModels列表。如果您想为NameViewModel提供一个可选的构造函数,以便传递一个Model,那也没问题。
我喜欢这种方法:
public NameViewModel : ViewModelBase
{
    public NameModel Model
    { 
        get { /* get stuff */ }
        set { /* set stuff */ }
    } 

    // Default constructor creates its own new NameModel
    public NameViewModel()
    {
        this.Model = new NameModel();
    }

    // Constructor has a specific model dictated to it
    public NameViewModel(NameModel model)
    {
        this.Model = model;
    }

    //Model wrapper properties
    public String Name
    { 
        get { return Model.Name; }
        set { Model.Name = value; }
    }
}

并且...

public class MainWindowViewModel
{
    Private ObservableCollection<ViewModel> _NameViewModels;

    Public Void MainWindowViewModel()
    {
        //Lets pretend we have a service that returns a list of models
        var nameModels = Service.Request();
        foreach(Model model in nameModels)
        {
            ViewModel viewmodel = new NameViewModel(model);
            NameViewModel.Add(viewmodel);
        }
    }

    Public ObservableCollection<ViewModel> NameViewModels
    {
        get
        {
            return _NameViewModels;
        }
    }
}

这样,您的MainWindowViewModel就不需要保留Models的完全独立副本,它只跟踪NameViewModels。每个NameViewModel负责其自己的底层模型,同时在构造期间仍可选择传递特定的模型。


嘿,谢谢你。这可能是处理问题最实用的方法。然而,我可能会遇到一个问题,当我将模型提交到文件/服务时,我该如何使用这些模型?ViewModels私下持有Models,因此没有“简单”的方法来准备好模型以提交到文件等。在这种情况下,您是否会公开暴露模型? - Adam Kewley
是的,我绝对会将模型公开为公共属性。 - BTownTKD

1
所有与创建相关的问题都可以通过引入工厂设计模式来解决。 工厂将负责根据提供的模型创建视图模型
public class MainWindowViewModel
{
    private List<Model> _NamesModel;
    private ObservableCollection<ViewModel> _NamesViewModel;
            private IViewModelFactory factory;

    public void MainWindowViewModel(IViewModelFactory factory)
    {
        //Lets pretend we have a service that returns a list of models
        _NamesModel = Service.Request();
        _NamesViewModel = factory.CreateNamesViewModels(_NamesModel);
    }

    public ObservableCollection<ViewModel> NamesViewModel
    {
        get
        {
            return _NamesViewModel;
        }
    }
}

此外,您甚至可以摆脱视图模型中的Service依赖,并将其移动到工厂本身,从而减少在视图模型中保留模型的需要(尽管必须承认,在更复杂的情况下,删除模型可能无法正常工作):
public ObservableCollection<ViewModel> CreateNamesViewModels()
{
    var models = Service.Request();
    return new ObservableCollection(models.Select(m => new ViewModel(m)));
}

此外,您的主窗口视图模型可以公开{{link1:commands}},利用工厂创建任何新实例。这样,没有模型泄漏到视图中,也没有暴露创建细节(因为命令将隐藏实际实现)。

1
啊,这是一个非常干净的实现,我喜欢它!我一直在将许多工厂的能力融入我的MainWindowViewModel中(我在MainWindowViewModel中有删除、添加等方法)。谢谢你! - Adam Kewley
我现在正在尝试这个实现。使用它,您是否建议MainWindowViewModel不处理任何模型?也就是说,工厂处理模型并只为MainWindowViewModel提供必要的ViewModel来处理?在这种情况下,MainWindowViewModel ViewModel集合应该具有CollectionChanged处理程序,利用工厂吗? - Adam Kewley
@AdamKewley:就像我所指出的,当模型很复杂时,您可能希望保留模型。除非是这种情况,否则我会在视图模型中摆脱模型。PropertyChangedObservableCollection通常足够了。 - k.m

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