PersonViewModel inherits from Person - clever or code smell?
In my ASP.NET app, I have a Person
and a PersonViewModel
.
My Person
was generated by LinqToSql
. It has the properties that will be persisted in the database.
My PersonViewModel
has everything Person
has, plus a couple "select lists", which will be used to populate combo boxes, as well as a FormattedPhoneNumber
(which is basically a PhoneNumber
with dashes and parenthases added).
I originally just made Person
a property on my PersonViewModel
, but I was thinking that would mean the page would have to "know" whether something was a Person
property or PersonViewModel
property. For example, for name, the view would request pvm.Person.Name
, but for the phone number, the view would request pvm.FormattedPhoneNumber
. If I use inheritance, then everything the view needs would always be a direct property of the view model, hence pvm.Name
.
This sounds great, but, there is no real "is-a" r开发者_如何学运维elationship here (i.e., I don't really think it makes sense to say "a PersonViewModel
is a Person
), and it seems to fly in the face of "preferring composition over inheritance". Still, I'm having difficulty thinking of a scenario where I'd need the ability to swap out a Person
for something else. If I do this, it would no longer be a PersonViewModel
.
What say you? Inherit from Person
or keep Person
as a property (or something else entirely)? Why?
Update
Thanks for all the answers.
It looks like the inheritance idea has been almost universally rejected, and for some sound reasons:
Decoupling the classes allows the
ViewModel
to contain only the properties from the domain model that are needed, plus any additional properties. With inheritance, you naturally expose all public properties from the domain model, which may not be a good idea.The
ViewModel
doesn't automatically need to change just because the domain model changed.As Jay mentioned, decoupling the
ViewModel
facilitates view-specific validation.As Kieth mentioned, using a Mapper (such as AutoMapper) can eliminate a lot of the tedious work in mapping common properties between classes.
Deriving from an object implies an "is a" relationship. So in this case you would effectively be saying that a PersonViewModel
is a Person
. It seems that this is not likely to be the semantics that you really want to convey, so use composition instead of inheritance here.
In reality it probably doesn't really affect the maintainability of the application (other than having a few more dots here and there) but using composition certainly feels cleaner to me. In addition, if it's a PersonViewModel
then presumably the view should know that that's the type it is dealing with.
Make Person
a private member, and expose the properties that your view requires in the PersonViewModel
, even if those properties just pass through the corresponding property of Person
.
pvm.Person.Name
is the code smell here.
Edit:
You're also limiting yourself to client-side validation and/or validation in the domain model, the latter meaning that if you create a second or subsequent ViewModel for Person, you can't change the validation on Person's members. (Well, you could, but this is a violation of the open-closed principle and you're introducing view concerns into the domain.) By hiding your domain objects from the view, you give yourself a clean space to perform use-case-specific validation before changes get made to your domain object. The domain object may have its own set of validators, but they can be strictly domain-appropriate, without view-specific issues creeping in.
You should not inherit from Person or use composition to have Person on PersonViewModel. Instead you should add the properties that you need to the PersonViewModel and map between them. Tools like AutoMapper (http://www.codeplex.com/AutoMapper) make this dead simple.
You should not expose your domain model directly to the View for a number of reasons, including security (under-posting and over-posting).
I am thinking a partial class may be better here. keep the generated Person class and add anothe partial Person class with the extra stuff in it.
On the one hand:
What if you did this?
Person p = new PersonViewModel { //init some properties };
Would p
do everything you'd expect a Person
to do? If it would, then sure, use inheritance. If it would have some peculiarities related to the fact that it's really a PersonViewModel, and not a Person, then use composition.
On the other hand:
My inclination is to use inheritance largely as a way of avoiding lots of duplicated code. Since you're only inheriting from one parent to one child (rather than to many children), you're not avoiding a lot of duplicated code in the first place. So it's probably not worth it to use inheritance.
One trick I've used, not specifically with ASP.NET MVC but with a similar use case, is to create a class that contains specifically those items specific to the ViewModel class (i.e., those that are not just tunneling through to the person class), and supplying an extension property on the Person class to allow access to the extended properties. This isn't strictly a ViewModel in the classic sense of the word, but I feel it allows for much of the same functionality without introducing awkward inheritance or code duplication with tunneled properties.
Here's a quick example of what I'm talking about:
public static class PersonExtensions {
public PersonViewData ViewData(this Person p) {
return new PersonViewData(p);
}
}
public class PersonViewData {
public PersonViewData(Person p) {
this._person = p;
}
private Person p;
public string FormattedPhoneNumber {
get { return p.PhoneNumber.ToPrettyString(); // or whatever }
}
}
精彩评论