【问题标题】:viewmodel inheritance and duplicate model references视图模型继承和重复模型引用
【发布时间】:2012-05-23 16:13:37
【问题描述】:

我的问题是:如何管理视图模型的继承链?

我的情况:

我有一个标准的 ViewModelBase,它只实现了 INotifyPropertyChanged 接口。

此外,我有一个 BusinessObjectViewModel 有一个 Guid,一个 PersonBaseViewModel 有个人核心数据,一个 CustomerViewModel 有客户相关的东西和EmployeeViewModel 与员工相关的东西。

所有视图模型当然都封装了一个模型对象(Customer、Employee、PersonBase)。

  • BusinessObjectViewModel 继承自 ViewModelBase
  • PersonBaseViewModel 继承自 BusinessObjectViewModel
  • CustomerViewModel 继承自 PersonBaseViewModel
  • EmployeeViewModel 继承自 PersonBaseViewModel

模型通过构造函数进入视图模型。

如果我使用构造函数链(每个视图模型都调用基本构造函数),每个视图模型都有它的模型来从模型返回封装的值。

但我必须在每个视图模型中都有一个模型属性。对于 CustomerViewModel,我将在 CustomerViewModel 中有一个引用,一个在 PersonBaseViewModel 中,一个在 BusinessObjectViewModel 中用于同一个对象。这对我来说听起来很愚蠢。

或者我必须在上层视图模型中转换每个属性访问。

附言这只是我模型层次结构的一小部分。

提前致谢。

【问题讨论】:

    标签: c# wpf inheritance mvvm viewmodel


    【解决方案1】:

    IMO 最简单的答案是使用泛型,这可能很简单

    public abstract class ViewModelBase<TModel>  TModel : class{
        public TModel Model { get; protected set; }
    }
    

    .net 类型系统将知道您的 TModel 是 Person、Customer 或其他任何内容,而无需强制转换。

    如果您需要更多信息,或者您想发布一些需要帮助的代码,请告诉我。是的,一开始要让你的超类型层次结构恰到好处可能会很棘手。

    HTH,
    浆果

    【讨论】:

    • 但这要求层次结构中的每个 ViewModel 类都是通用的。这可能会导致一些奇怪的情况,例如 BusinessObjectViewModel 或 BusinessObjectViewModel,不应允许 ViewModel 和 Model 类的组合。
    【解决方案2】:

    如果 BusinessObject 和 Person 类(及其对应的 VM)是抽象的,那么您可以像这样访问正确的模型:

    public abstract class BusinessObjectViewModel : ViewModelBase
    {
        protected abstract BusinessObject BusinessObject { get; }
    
        protected BusinessObject Model { get { return this.BusinessObject; } }
    }
    
    public abstract class PersonViewModel : BusinessObjectViewModel
    {
        protected abstract Person Person { get; }
    
        protected new Person Model { get { return this.Person; } }
    
        protected override sealed BusinessObject BusinessObject
        {
            get { return this.Model; }
        }
    }
    
    public class CustomerViewModel : PersonViewModel
    {
        protected new Customer Model { get; set; }
    
        protected override sealed Person Person
        {
            get { return this.Model; }
        }
    }
    
    public class EmployeeViewModel : PersonViewModel
    {
        protected new Employee Model { get; set; }
    
        protected override sealed Person Person
        {
            get { return this.Model; }
        }
    }
    

    这样,每个派生的 VM 类通过实现抽象属性并隐藏基类 Model 属性为其基 VM Model 属性提供一个值,因此每个 VM 都使用适当类型的 Model 属性(因此不需要强制转换)。

    这种方法有其优点和缺点:

    好处:

    • 不涉及强制转换。

    缺点:

    • 仅当基类(BusinessObjectViewModel 和 PersonViewModel)为抽象时才有效,因为必须存在由派生类实现的抽象属性,并为这些基类提供模型实例。
    • 不应在基类构造函数中访问模型属性,因为构造函数链接从基类到最派生类。派生最多的类构造函数会设置Model,所以基类构造函数会被提前调用以查看它。这可以通过将 Model 作为参数传递给构造函数来避免。
    • 派生类不需要看到 BusinessObject 和 Person 属性。 EditorBrowsableAttribute 可能在此处对 Intellisense 有所帮助,但仅当代码由不同 Visual Studio 解决方案中的另一个程序集使用时(这是 Visual Studio 特定的行为)。
    • 性能。当基类访问模型时,代码会经过一个虚拟属性链。但由于已实现的抽象属性被标记为密封,因此虚拟表查找不应该降低性能。
    • 不能很好地扩展。对于深层类层次结构,代码将包含许多不必要的成员。

    另一种方法是:

    public class BusinessObjectViewModel : ViewModelBase
    {
        protected BusinessObject Model { get; private set; }
    
        public BusinessObjectViewModel(BusinessObject model)
        {
            this.Model = model;
        }
    }
    
    public class PersonViewModel : BusinessObjectViewModel
    {
        protected new Person Model { get { return (Person)base.Model; } }
    
        public PersonViewModel(Person model)
            : base(model)
        {
        }
    }
    
    public class CustomerViewModel : PersonViewModel
    {
        protected new Customer Model { get { return (Customer)base.Model; } }
    
        public CustomerViewModel(Customer model)
            : base(model)
        {
        }
    }
    
    public class EmployeeViewModel : PersonViewModel
    {
        protected new Employee Model { get { return (Employee)base.Model; } }
    
        public EmployeeViewModel(Employee model)
            : base(model)
        {
        }
    }
    

    好处:

    • 基类不需要是抽象的。
    • 可以通过基类构造函数访问模型。
    • 没有不必要的附加属性。

    缺点:

    • 铸造。

    基于此分析,我会选择第二种选择,因为修复其唯一的缺点,即强制转换性能,将是不必要的微优化,在 WPF 上下文中不会被注意到。

    【讨论】:

    • 嗨。谢谢您的回答。很好的解释。我选择了第二种解决方案。只需对我当前的数据模型进行一些更改。
    【解决方案3】:

    如果您只想在 ViewModel 中公开 Model 属性,则无需在 ViewModel 中重新声明 Model 属性即可公开它们。我通常将底层模型对象作为我的 ViewModel 中的属性公开。在您的情况下,例如在您的 EmployeeViewModel 中,您将拥有:

    private Employee _MyEmployee;
    public Employee MyEmployee {
    get
    {
    return _MyEmployee;
    }
    set
    {
    _MyEmployee = value;
    NotifyPropertyChanged(x=>x.MyEmployee);
    }
    

    然后,您的 View 可以通过 ViewModel 中公开的 MyEmployee 属性绑定到您的 Employee 属性。据我了解,您想要在 VM 中重新声明或包装模型属性的唯一情况是您需要进行一些数据操作以呈现给您的视图。

    【讨论】:

    • 这正是我正在做的。但是 BusinessObjectViewModel 需要一个模型来访问 Id(类型 System.Guid)。而继承自 BusinessObjectViewModel 的 PersonViewModel 需要该模型来访问人员的属性。我知道模型属性是继承到 PersonViewModel 的。但是要么我在 PersonViewModel 中强制转换每个属性访问,要么 PersonViewModel 获得对该对象的自己的引用,这导致同一对象的 2 个属性。
    • 为什么不创建一个 BusinessObjectBase 或 ModelBase 类,这将是您的模型基类。这样,如果您的所有模型都从该类继承,您可以在 BusinessObjectViewModel 中封装一个 ModelBase 类型属性,而不必在派生的 ViewModel 中重新声明它?
    • 我也是这样做的。我想我提供的信息太少了 :) 所有视图模型都存在相应的模型。 BusinessModelBase、人员、员工、客户。 BusinessModelBase 有Id,Person 有persondata 并继承自BusinessModel 等等。但问题是,在 PersonViewModel 中,Model 属性属于 BusinessModelBase 类型。所以我必须强制转换每个属性访问(模型作为人).SurName。但我不想那样做。我想我正在寻找继承以外的解决方案。也许给专业人士(客户)一个 Person 属性。
    • 查找“动态属性”部分here。您需要在哪里进行类型转换?我认为,如果您在 View XAML 中绑定到 MyModelBaseProperty.SurName 之类的东西,它会起作用,它会根据我所做的一些测试自动处理转换。
    • 如果您使用的是 .NET 4.0,您甚至可以考虑将封装的 Model 属性声明为“动态”类型,这样您根本不必进行强制转换。我很想知道它在 MVVM 中是如何工作的。
    猜你喜欢
    • 2019-01-27
    • 1970-01-01
    • 2014-11-03
    • 2014-10-04
    • 1970-01-01
    • 2015-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多