如何从其包装的 ViewModel 中获取 Model 实例?

How do I get the Model instance from inside its wrapping ViewModel?

简短版本:

If I have ViewModel, containing its Model object and exposing its properties, how do I get the model "back" after it has been edited? If the Model-inside-ViewModel is public, it violates encapsulation, and if it is private, I cannot get it (right?).


更长的版本:

我正在实现显示对象集合的应用程序的一部分。假设对象的类型为 Gizmo,它在模型层中声明,并简单地保存属性并处理其自身的 serialization/deserialization.

在模型层,我有一个Repository<T> class,我用它来处理MasterGizmoDetailGizmo的集合。此存储库 class 的属性之一是 IEnumerable<T> Items { get; },其中 T 将是某些 Gizmo 子类型。

现在由于 Gizmo 没有实现 INPC,我在 ViewModel 层中创建了以下 classes:

注意 Model 层有一个 "Repository of Models",而 ViewModel 层有一个 "ViewModel with an ObservableCollection of ViewModels".

疑点与上面的[**]部分有关。我的RepositoryViewModel.CollectionChangedHandler方法如下:

    void CollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                foreach (var added in e.NewItems)
                {
                    var gvm = added as GizmoViewModel;
                    if (gvm != null)
                    {
                         //// IS ANY OF THE ALTERNATIVES BELOW THE RIGHT ONE?
                         // Gizmo g = gvm.RetrieveModel();          ?? proper getter ??
                         // Gizmo g = GetModelFromViewModel(gvm);   ?? external getter ??
                         // Gizmo g = gvm.Model;                    ?? public model property ??

                        _gizmo_repository.Add(g);
                    }
                }
                break;
            ....

除此之外,如果有人能在这里检测到任何 MVVM 气味,我将很高兴知道。

据我所见,您的 GizmoViewModel 依赖于您的 Repository<T>,所以为什么不在创建视图模型时传入存储库?

public class GizmoViewModel
{
    private IRepository<Gizmo> _Repo;

    //Underlying model (Doesn't implement INotifyPropertyChanged)
    private Gizmo _Model;

    //Wrapping properties
    public int MyProperty
    {
        get { return _Model.Property; }
        set
        {
            _Model.Property = value;
            NotifyOfPropertyChange();
        }
    }

    ...

    public GizmoViewModel(IRepository<Gizmo> repo)
    {
        _Repo = repo;
    }

    public void AddToRepo()
    {
        _Repo.Add(_Model);
    }

    ...

如果这些方法在 RepositoryViewModel 基础 class 中, 会更好。你真的可以在这里疯狂继承。也许是这样的:

var gvm = added as IRepositoryViewModel;

if (gvm != null)
    gvm.AddToRepo();

当您需要将视图模型的基础模型添加到存储库时,您只需调用 AddToRepo

也许这不是最优雅的解决方案,但是如果封装是您所担心的,那么您需要确保您的依赖关系得到妥善管理。

阅读您的代码,我认为您的 ViewModel 和 Model 分离有些混乱。

因此,据我了解,当您的 GizmoViewModel 的 ObservableCollection 发生变化时,您是否正在尝试将新项目的 Gizmo 实例添加回您的模型?

我会以不同的方式处理这个问题。您应该在您的模型层中创建您的 Gizmo 实例,并且当您这样做时,您应该将它添加到存储库中。

否则,您没有提供足够的信息 - 或者更确切地说,您提供的信息太多了,但这是错误的信息类型。您需要描述您想要执行此操作的情况,创建这些 GizmoViewModel 的位置等。

"If the Model-inside-ViewModel is public, it violates encapsulation"

你上面的断言是完全错误的,并且正在杀死你的代码。
通过将 ViewModel 中的模型 属性 设置为私有,您将被迫重复自己(代码味道),因为您需要在 ViewModel 中定义与您为模型所做的相同的属性,有效地将其转换为一个模型 class 模仿它应该暴露给视图的模型。

在 MVVM 中,ViewModel 的作用是为 View 提供它需要的所有表示数据和逻辑,并且确保 Model 是这些数据的基本部分,通过将它从 View 中隐藏你正在杀死 MVVM。

我们甚至可以在 View 和 ViewModel 层之外处理我们的模型,因此我认为让模型可以从 ViewModel 公开访问是可以接受的。

假设您正在 "DataLayer" 中创建模型,您可以将模型的实例传递给 ViewModel。为了说明我的观点:

///Models ////////////////////////////
public interface IGizmo{}
public class Gizmo:IGizmo{}
public class SuperGizmo : IGizmo {}
public class SuperDuperGizmo : IGizmo { }
//////////////////////////////////////

public interface IGizmoViewModel<out T>
{
    T GetModel();
}

public abstract class GizmoViewModelBase : IGizmoViewModel<IGizmo>
{
    protected GizmoViewModelBase(IGizmo model)
    {
        _Model = model;
    }

    private readonly IGizmo _Model;
    public IGizmo GetModel()
    {
        return _Model;
    }
}

public class GizmoViewModel : GizmoViewModelBase
{
    public GizmoViewModel(Gizmo model)
        : base(model) { }
}

public class SuperDuperGizmoViewModel : GizmoViewModelBase
{
    public SuperDuperGizmoViewModel(SuperDuperGizmo model)
        : base(model){}
}

只要您传递了相同的实例,您的模型存储库就会根据从 ViewModel 获得的任何更新进行更新。因此,无需 ViewModel 存储库即可获取更新。