\\\关键要点
\\
- 可变模型应该具备自我验证的能力,并实现验证接口。\\t
- 在共享对象时(特别是在跨线程共享时),考虑使用不可变模型。\\t
- 考虑支持MVVM风格UI的单层和多层撤消。\\t
- 在实现属性变更通知时避免不必要的内存分配。\\t
- 不要覆盖模型的Equals和GetHashCode方法。\
在传统的MVC、MVP、MVVM、Web MVC这些UI模式中,模型是一个公共元素。虽然有很多文章讨论这些架构中的视图和控制器,但几乎无一涉及模型。在本文中,我们将讨论模型本身以及相应的.NET接口。
\\我想先定义一些术语,这些术语在其他文章中可能有更精确的定义,但对于我们来说这些已经足够了。
\\数据模型(Data Model)
\\数据模型时包含数据(即属性和集合)和行为的对象或对象图。数据模型是本文的重点。
\\数据传输对象(Data Transfer Object,DTO)
\\DTO是只包含属性和集合的对象或对象图。一个真正的DTO没有任何行为,而且几乎是不可变的。
\\不过,在使用代码生成工具生成DTO时,通常会使用一些简单的接口(如INotifyPropertyChanged)。
\\对象图(Object Graph)
\\一个对象图由一个对象和所有可触及的子对象组成。在讨论数据模型和DTO时,我们所说的对象图都是单向树状结构(循环图是存在的,但它们会对序列化框架造成影响)。
\\领域模型(Domain Model)
\\领域模型是描述一组相关数据模型的更高级概念。
\\实体(Entity)
\\术语“实体”有许多定义,其中一些与“数据模型”基本相同。随着nHibernate和Entity Framework的流行,这个术语一般是指与数据库表一对一映射的DTO。
\\基于这个定义,实体可以用属性来修饰,以便更精确地描述数据库列和属性之间的映射关系。它还支持从数据库延迟加载子集合。
\\虽然可以通过扩展让实体承担数据模型的角色,但在应用业务逻辑之前,将实体映射到单独的数据模型或DTO是更为常见的做法。
\\业务实体(Business Model)
\\不要与ORM的实体混淆了,这是数据模型的另一种呈现方式。
\\不可变对象(Immutable Object)
\\不可变对象不包含可以改变属性的方法,它本身不是数据模型,但它可能出现在表示静态查找数据的数据模型中。因为它们不能被修改,所以跨多个数据模型共享一个不可变对象是安全的。
\\数据访问层(Data Access Layer,DAL)
\\在本文中,DAL包含了服务对象、存储库、直接数据库调用、Web服务调用等。基本上包括了任何用于与外部依赖项(如数据存储)发生交互的东西。
\\\\真正的数据模型是可确定性测试(deterministically testable)的。也就是说,它们只由其他可确定性测试的数据类型组成。这意味着数据模型在运行时不能有任何外部依赖关系。
\\最后一点很重要。如果一个类在运行时与DAL耦合,那么它就不是数据模型。即使在编译时使用IRepository接口来“解耦”类,也无法消除与外部依赖的关系。
\\在判断什么是数据模型时,要小心那些“存活实体”。为了支持延迟加载,来自ORM的实体通常会包含一个对数据库上下文的引用。这就又让我们回到了非确定性行为的领域,实体行为的变化取决于上下文状态以及对象的创建方式。
\\换句话说,数据模型的所有方法都应该是可预测的,而且这种预测只能基于它们的属性值。
\\\\父对象和子对象通常需要交互。如果做得不好,可能会导致难以理解的紧密交叉耦合。为了简化问题,请遵循以下三条规则:
\\- 父对象可以直接与子对象的属性和方法交互。\\t
- 子对象只能通过触发事件与父对象进行交互。\\t
- 对象不能直接与兄弟对象交互,兄弟对象之间的消息必须通过共同的父对象来传递。\
基于这样的设计,可以将子对象分解出来,并在没有父对象的情况下对其进行测试。测试本身可以监控只有父对象能够处理的事件。
\\\\接下来我想谈谈数据模型可能会实现的可选特性。但在开始之前,我想先讨论每个数据模型必须具备的一个特性:验证。
\\完全不处理数据的数据模型几乎是不存在的。如果模型是来自文件、外部应用程序或用户界面,就有可能会引入不一致或不合法的值。来自用户界面的问题会更多,因为用户通常需要逐个字段得填写表单。
\\因为存在这些限制,所以不能在构造函数和属性设置器中使用异常,就像你在其他类中使用异常一样。不过可以验证接口,为错误检查提供一些灵活性。
\\.NET提供了一些开箱即用的验证接口,不过每个人都有自己特定的需求。
\\IDataErrorInfo
\\IDataErrorInfo接口早就可以用了,不过现在基本被弃用,因为它用起来很麻烦。让我们来看看它的属性。
\\string Error {get;}:这个属性有三个用途:
\\- 报告对象级别的错误\\t
- 报告所有属性级别的错误\\t
- 通过返回一个空字符串来表示不存在错误\
string this[string columnName] {get;}:这个索引器属性将返回属性特定的错误。
\\正如你所看到的,Error属性做的事情太多了,