viewmodel inheritance and duplicate model references

痞子三分冷 提交于 2019-12-23 03:10:06

问题


My question is: How to manage an inheritance chain of viewmodels?

My situation:

I'm having a standard ViewModelBase which only implements the INotifyPropertyChanged interface.

Furthermore I have a BusinessObjectViewModel which has a Guid, a PersonBaseViewModel which has the persons core data, a CustomerViewModel with customer related stuff and a EmployeeViewModel with employee related stuff.

All viewmodels certainly encapsulate a modelobject (Customer, Employee, PersonBase).

  • BusinessObjectViewModel inherits from ViewModelBase
  • PersonBaseViewModel inherits from BusinessObjectViewModel
  • CustomerViewModel inherits from PersonBaseViewModel
  • EmployeeViewModel inherits from PersonBaseViewModel

The model comes via constructor into the viewmodel.

If I use a constructor chain (every viewmodel makes a call to the base constructor) every viewmodel has it's model to return the encapsulated values from the model.

But I got to have a Model property in every viewmodel. In the case of CustomerViewModel I would have a reference in CustomerViewModel, one in PersonBaseViewModel and one in BusinessObjectViewModel for one and the same object. That sounds stupid to me.

Or I have to cast every property access in the upper viewmodels.

p.s. this is just a small cut-out of my model hierarchy.

Thanks in advance.


回答1:


If BusinessObject and Person classes (and their VM counterparts) are abstract, then you could access the right Model like this:

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; }
    }
}

This way every derived VM class provides a value for its base VM Model property by implementing abstract property and hides a base class Model property, so every VM works with Model property of an appropriate type (so no casting is required).

This approach has its benefits and drawbacks:

Benefits:

  • No casting involved.

Drawbacks:

  • Works only if base classes (BusinessObjectViewModel and PersonViewModel) are abstract because there must exist an abstract property that is implemented by the derived class and provides a Model instance to these base classes.
  • Model property should not be accessed in the base class constructors, because constructor chaining goes from base class to the most derived class. The most derived class constructor will set Model, so base class constructors are called to early to see it. This can be avoided by passing Model as a parameter through constructors.
  • BusinessObject and Person properties are unnecessary seen by the derived classes. EditorBrowsableAttribute might help here for Intellisense, but only when code is used by another assembly in different Visual Studio solution (this is Visual Studio specific behavior).
  • Performance. When base classes access Model, code will go through a chain of virtual properties. But since since implemented abstract properties are marked as sealed, virtual table lookup should not be so much performance degrading.
  • Doesn't scale nicely. For deep class hierarchies code would contain many unnecessary members.

Another approach would be:

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)
    {
    }
}

Benefits:

  • Base classes do not need to be abstract.
  • Model can be accessed through base class constructors.
  • No unnecessary additional properties.

Drawbacks:

  • Casting.

Based on this analysis, I would go with a second option because fixing its only drawback, casting performance, would be unnecessary micro-optimization that would not be noticeable in WPF context.




回答2:


The simplest answer IMO is to use Generics, which might be as simple as

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

The .net typing system will the know that your TModel is a Person, Customer, or whatever else without casting.

Let me know if you need more or if you want to post some code that needs help. And yes, it can be tricky getting your supertype heirarchies just right at first.

HTH,
Berryl




回答3:


If you just want to expose your Model property in your ViewModels then you don't need to re-declare the Model properties in the ViewModel to expose them. I normally expose the underlying Model object as a property in my ViewModels. In your case, for example in your EmployeeViewModel you would have a:

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

Then your View could bind to your Employee properties through the MyEmployee property exposed in the ViewModel. As far as I understand it, the only case when you want to re-declare or wrap your Model Properties in your VM is when you need to do some data manipulation to be presented to your view.



来源:https://stackoverflow.com/questions/10723922/viewmodel-inheritance-and-duplicate-model-references

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!