【问题标题】:PersonViewModel inherits from Person - clever or code smell?PersonViewModel 继承自 Person - 聪明还是代码味道?
【发布时间】:2010-01-28 20:09:05
【问题描述】:

在我的 ASP.NET 应用程序中,我有一个 Person 和一个 PersonViewModel

我的Person 是由LinqToSql 生成的。它具有将持久保存在数据库中的属性。

我的PersonViewModel 拥有Person 所拥有的一切,加上几个“选择列表”,它们将用于填充组合框,以及一个FormattedPhoneNumber(基本上是一个带有破折号和括号的PhoneNumber添加)。

我最初只是在我的PersonViewModel 上创建了Person 属性,但我认为这意味着页面必须“知道”某个东西是Person 属性还是PersonViewModel 属性。例如,对于姓名,视图将请求pvm.Person.Name,但对于电话号码,视图将请求pvm.FormattedPhoneNumber。如果我使用继承,那么视图所需的一切都将始终是视图模型的直接属性,因此pvm.Name

这听起来不错,但是这里没有真正的“is-a”关系(即,我认为说“a PersonViewModel is a @987654337 @),而且它似乎在“更喜欢组合而不是继承”的情况下飞行。不过,我很难想到我需要能够将Person 换成其他东西的能力。如果我这样做,它将不再是PersonViewModel

你说什么?Person 继承或将Person 保留为属性(或完全其他)?为什么?

更新

感谢大家的回答。

看起来继承的想法几乎被普遍拒绝了,并且出于一些合理的原因:

  1. 解耦类允许ViewModel 包含来自域模型的所需属性以及任何其他属性。通过继承,您自然会从域模型中公开所有公共属性,这可能不是一个好主意。

  2. ViewModel 不会因为域模型更改而自动更改。

  3. 正如 Jay 所说,解耦 ViewModel 有助于特定于视图的验证。

  4. 正如 Kieth 所提到的,使用 Mapper(例如 AutoMapper)可以消除在类之间映射公共属性时的大量繁琐工作。

【问题讨论】:

  • 无论如何,过于“聪明”的代码几乎总是一种代码味道。
  • 只有当聪明涉及到扭曲或卷积时。 干净 聪明的代码……很酷吗? (不是我想用的词,但我决定使用头韵)

标签: c# asp.net-mvc design-patterns


【解决方案1】:

从对象派生意味着“是”关系。所以在这种情况下,您实际上会说PersonViewModelPerson。看来这不太可能是您真正想要传达的语义,所以在这里使用组合而不是继承。

实际上,它可能不会真正影响应用程序的可维护性(除了在这里和那里有更多的点),但使用组合对我来说确实感觉更干净。此外,如果它是PersonViewModel,那么大概视图应该知道它正在处理的类型。

【讨论】:

  • 这正是 OP 在问题中所说的。
  • Jay - 很难不重复他的问题,因为他已经有效地回答了自己的问题,而且似乎只是在寻找确认组合确实是更清洁的方法。我添加了一些更多的叙述,希望能增加一些价值。
  • Greg,我实际上是在寻找在这种特殊情况下继承是否“适当”的确认,但我很高兴被谈论它:) 我不确定我是否同意“视图应该知道 [Person] 是它正在处理的类型”。我在想,无论我从控制器返回什么,都是视图正在处理的类型。因此,在这种情况下,PersonViewModelPerson 的超集,是视图正在处理的类型。
  • 我认为他是说视图应该知道它正在处理PersonViewModel Person
  • 两者之间的映射仍然是比使用组合更好的答案。尤其是在绑定回来的时候,你不应该直接使用你的领域模型。
【解决方案2】:

Person 设为私有成员,并在PersonViewModel 中公开您的视图所需的属性,即使这些属性只是通过Person 的相应属性。

pvm.Person.Name 是这里的代码味道。


编辑:

您还将自己限制在客户端验证和/或域模型中的验证,后者意味着如果您为 Person 创建第二个或后续 ViewModel,则无法更改对 Person 成员的验证。 (嗯,你可以,但是这违反了开放-封闭原则,并且你将视图关注点引入到域中。)通过从视图中隐藏你的域对象,你给自己一个干净的空间来执行用例-在对域对象进行更改之前进行特定验证。域对象可能有自己的一组验证器,但它们可以严格适用于域,而不会出现特定于视图的问题。

【讨论】:

  • +1 用于满足得墨忒耳定律并将 LinqToSql 对象保持在尽可能有限的范围内。可能没有必要更换一个人,但如果你需要一个不同的人提供者,那么你可能会遇到一些痛苦。
  • ...如果其他人有可能接触到这段代码,他们很容易创建一个使用 pvm.Person.SocialSecurityNumber 的视图。对于智能感知用户,它似乎是 API 的一部分。也许该属性现在不存在,但如果添加它,它将通过您的视图模型变得可见。与私人成员的组合使每个财产的曝光成为一个深思熟虑的选择。
  • 谢谢你,杰伊,+1。您的回答可能是我在大多数情况下自然会做的,所以我对此很满意。此外,我对如何处理“视图验证”与“模型验证”有点不确定,您的编辑也提供了一些明确性。
【解决方案3】:

您不应从 Person 继承或使用组合在 PersonViewModel 上拥有 Person。相反,您应该将所需的属性添加到 PersonViewModel 并在它们之间进行映射。 AutoMapper (http://www.codeplex.com/AutoMapper) 之类的工具让这一切变得非常简单。

出于多种原因,您不应将域模型直接暴露给视图,包括安全性(发布不足和过度发布)。

【讨论】:

  • 我很感兴趣。从文档中我不太确定——这是一个“扁平化”的场景吗?您是否愿意提供一个在这种情况下如何使用 AutoMapper 的示例?我的PersonViewModel 需要具有与Person 相同的所有属性(NamePhoneNumberPhoneNumberTypeRole),加上两个SelectList 属性(PhoneNumberTypesRoles)和FormattedPhoneNumber。如何使用 AutoMapper 构建我的 ViewModel
  • 如果您拥有所有相同的属性,那么您只需创建域模型的镜像以及 2 个选择列表。在 AutoMapper 配置中,您只需说映射 2 个实体,只要属性名称相同,它就会自动执行此操作。当属性之间存在某种转换时,AutoMapper 真的很出色,因为您可以在一个配置类中定义您的转换。不过,在您的情况下,这很简单。此外,在 ViewModel 中没有域模型允许您更改独立于域模型的 ViewModel。
  • @Kieth,谢谢,我试试看。
  • @Kieth,我一直在使用 AutoMapper,但在尝试在模型对象和视图模型之间进行转换时遇到了很多麻烦。然后我从 Jimmy Bogard 本人那里发现了这个答案,stackoverflow.com/questions/1701801/…。所以现在,我必须重新考虑这个问题。有什么想法吗?
  • 我认为 Jimmy 所解决的问题仅与 SelectLists 有关。他是说您不想使用 AutoMapper 为这些进行转换。我通常为 SelectLists 做的是在我的 ViewModel 上有一个属性,它只是一个 getter,它将针对其源执行我的 LINQ 查询(这也是在 ViewModel 上设置的属性。
【解决方案4】:

我认为这里的部分课程可能会更好。保留生成的 Person 类并添加另一个包含额外内容的部分 Person 类。

【讨论】:

  • 这是一个很好的建议,与我的想法完全一样。这使您可以在架构更改时重新生成 Person 模型,并将所有额外的方法保留在 Person 所属的位置。
  • @John & @Pharabus,我也想过这个问题,但我反对这种情况下的部分类:为什么我的Person 类(这是一个模型类)应该知道什么特定视图的特定需求?如果我想支持使用不同自定义字段的替代视图,这是否意味着我应该继续添加到我的部分类?最终,我的Person 类将被一堆与视图相关的属性污染,这些属性在模型层中确实没有业务。
  • 是的,你说得对,我并没有真正考虑过对 Person 有不同的看法。在那种情况下,我认为组合模型最有意义(PersonModelView 有一个人)。
  • 您的 ViewModel 和 Domain 模型应该解耦,以便它们可以独立地进行版本控制。这样,如果其中任何一个发生更改,那么您不一定需要更改另一个 - 您只需要更新映射。
【解决方案5】:

一方面:

如果你这样做了呢?

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

p 会做你希望Person 做的所有事情吗?如果可以,那么肯定会使用继承。如果它有一些与它实际上是 PersonViewModel 而不是 Person 的事实相关的特殊性,那么使用组合。

另一方面:

我倾向于使用继承来避免大量重复代码。由于您只是从一个父母继承给一个孩子(而不是多个孩子),因此您一开始并没有避免大量重复的代码。所以使用继承可能不值得。

【讨论】:

    【解决方案6】:

    我使用过的一个技巧,不是专门针对 ASP.NET MVC,而是针对类似的用例,是创建一个类,该类专门包含特定于 ViewModel 类的那些项目(即那些不只是通过隧道连接到person 类),并在 Person 类上提供扩展属性以允许访问扩展属性。这并不是严格意义上的经典视图模型,但我认为它允许许多相同的功能,而不会引入尴尬的继承或带有隧道属性的代码重复。

    下面是我所说的一个简单示例:

    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 的想法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-29
    • 2010-12-13
    • 2017-12-13
    • 2016-05-04
    相关资源
    最近更新 更多