PersonViewModel наследует от Person - умный или запах кода? - PullRequest
2 голосов
/ 28 января 2010

В моем приложении ASP.NET у меня есть Person и PersonViewModel.

Мой Person был сгенерирован LinqToSql. У него есть свойства, которые будут сохранены в базе данных.

В моем PersonViewModel есть все, что есть у Person, плюс пара «списков выбора», которые будут использоваться для заполнения полей со списком, а также FormattedPhoneNumber (в основном это PhoneNumber с тире и круглыми скобками). добавленный).

Первоначально я просто сделал Person свойством для PersonViewModel, но я думал, что это означало бы, что страница должна будет "знать", является ли что-то Person свойством или PersonViewModel свойством. Например, для имени представление будет запрашивать pvm.Person.Name, но для номера телефона представление будет запрашивать pvm.FormattedPhoneNumber. Если я использую наследование, то все, что нужно представлению, всегда будет прямым свойством модели представления, поэтому pvm.Name.

Это звучит замечательно, но здесь нет настоящих отношений "есть" (то есть, я не думаю, что имеет смысл говорить: "PersonViewModel - это a Person ), и это, кажется, идет вразрез с «предпочтением композиции перед наследованием». Тем не менее, мне трудно думать о сценарии, в котором мне понадобится возможность поменять Person на что-то другое. Если я это сделаю это больше не будет PersonViewModel.

Что скажете вы? Унаследовать от Person или оставить Person как свойство (или что-то еще целиком)? Почему?

Обновление

Спасибо за все ответы.

Похоже, что идея наследования была почти повсеместно отвергнута, и по некоторым веским причинам:

  1. Разделение классов позволяет ViewModel содержать только требуемых свойств модели домена и любые дополнительные свойства. С помощью наследования вы, естественно, предоставляете все открытые свойства из модели предметной области, что может быть не очень хорошей идеей.

  2. ViewModel не нужно автоматически менять только потому, что изменилась модель домена.

  3. Как уже упоминал Джей, разъединение ViewModel облегчает проверку для конкретного вида.

  4. Как упоминал Киет, использование Mapper (например, AutoMapper) может избавить вас от утомительной работы по сопоставлению общих свойств между классами.

Ответы [ 6 ]

8 голосов
/ 28 января 2010

Вывод из объекта подразумевает отношение "есть". Таким образом, в этом случае вы бы фактически сказали, что PersonViewModel - это Person. Похоже, это не та семантика, которую вы действительно хотите передать, поэтому используйте здесь композицию вместо наследования.

На самом деле это, вероятно, не влияет на удобство сопровождения приложения (кроме того, что у него есть несколько точек здесь и там), но использование композиции, безусловно, кажется мне чище. Кроме того, если это PersonViewModel, то, вероятно, представление должно знать, что это тот тип, с которым он имеет дело.

6 голосов
/ 28 января 2010

Сделайте Person личным членом и предоставьте свойства, необходимые для вашего представления в PersonViewModel, даже если эти свойства просто проходят через соответствующее свойство Person.

pvm.Person.Name - это кодовый запах здесь.


Edit:

Вы также ограничиваетесь проверкой и / или проверкой на стороне клиента в модели домена, что означает, что если вы создадите вторую или последующую ViewModel для Person, вы не сможете изменить проверку для участников Person. (Ну, вы могли бы, но это нарушение принципа открытого-закрытого, и вы вводите проблемы представления в домен.) Скрывая свои доменные объекты от представления, вы даете себе чистое пространство для выполнения сценария использования специальная проверка перед внесением изменений в ваш объект домена. Доменный объект может иметь свой собственный набор валидаторов, но они могут строго соответствовать домену, без проблем, связанных с представлением.

2 голосов
/ 29 января 2010

Вы не должны наследовать от Person или использовать композицию, чтобы Person был включен в PersonViewModel. Вместо этого вы должны добавить нужные вам свойства в PersonViewModel и отобразить их между собой. Такие инструменты, как AutoMapper (http://www.codeplex.com/AutoMapper), упрощают эту задачу.

Вы не должны предоставлять свою модель домена непосредственно представлению по ряду причин, включая безопасность (недостаточная публикация и избыточная публикация).

1 голос
/ 28 января 2010

Один прием, который я использовал, не только с ASP.NET MVC, но с аналогичным сценарием использования, заключается в создании класса, который содержит конкретно те элементы, которые относятся к классу ViewModel (т. Е. Те, которые не просто туннелируют до класс person) и предоставление свойства расширения для класса Person, чтобы разрешить доступ к расширенным свойствам. Это не строго ViewModel в классическом смысле этого слова, но я чувствую, что он допускает большую часть тех же функций без введения неуклюжего наследования или дублирования кода с туннельными свойствами.

Вот краткий пример того, о чем я говорю:

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 }
     }
}
1 голос
/ 28 января 2010

С одной стороны:

Что если ты это сделал?

Person p = new PersonViewModel { //init some properties };

Будет ли p делать все, что вы ожидаете от Person? Если это так, то обязательно используйте наследование. Если у него есть некоторые особенности, связанные с тем, что это действительно PersonViewModel, а не Person, используйте композицию.

С другой стороны:

Я склонен использовать наследование в основном как способ избежать большого количества дублирующегося кода. Поскольку вы наследуете только от одного родителя от одного ребенка (а не от многих детей), вы в первую очередь не избегаете большого количества дублированного кода. Так что, вероятно, не стоит использовать наследование.

1 голос
/ 28 января 2010

Я думаю, что частичный класс может быть лучше здесь. сохраните сгенерированный класс Person и добавьте еще один частичный класс Person с дополнительным содержимым.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...