大橙子网站建设,新征程启航
为企业提供网站建设、域名注册、服务器等服务
关键要点
公司主营业务:网站制作、网站建设、移动网站开发等业务。帮助企业客户真正实现互联网宣传,提高企业的竞争能力。创新互联是一支青春激扬、勤奋敬业、活力青春激扬、勤奋敬业、活力澎湃、和谐高效的团队。公司秉承以“开放、自由、严谨、自律”为核心的企业文化,感谢他们对我们的高要求,感谢他们从不同领域给我们带来的挑战,让我们激情的团队有机会用头脑与智慧不断的给客户带来惊喜。创新互联推出新疆免费做网站回馈大家。可变模型应该具备自我验证的能力,并实现验证接口。
在共享对象时(特别是在跨线程共享时),考虑使用不可变模型。
考虑支持MVVM风格UI的单层和多层撤消。
在实现属性变更通知时避免不必要的内存分配。
不要覆盖模型的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的实体通常会包含一个对数据库上下文的引用。这就又让我们回到了非确定性行为的领域,实体行为的变化取决于上下文状态以及对象的创建方式。
换句话说,数据模型的所有方法都应该是可预测的,而且这种预测只能基于它们的属性值。
在父对象和子对象之间传递消息
父对象和子对象通常需要交互。如果做得不好,可能会导致难以理解的紧密交叉耦合。为了简化问题,请遵循以下三条规则:
基于这样的设计,可以将子对象分解出来,并在没有父对象的情况下对其进行测试。测试本身可以监控只有父对象能够处理的事件。
验证——数据模型唯一必须具备的功能
接下来我想谈谈数据模型可能会实现的可选特性。但在开始之前,我想先讨论每个数据模型必须具备的一个特性:验证。
完全不处理数据的数据模型几乎是不存在的。如果模型是来自文件、外部应用程序或用户界面,就有可能会引入不一致或不合法的值。来自用户界面的问题会更多,因为用户通常需要逐个字段得填写表单。
因为存在这些限制,所以不能在构造函数和属性设置器中使用异常,就像你在其他类中使用异常一样。不过可以验证接口,为错误检查提供一些灵活性。
.NET提供了一些开箱即用的验证接口,不过每个人都有自己特定的需求。
IDataErrorInfo
IDataErrorInfo接口早就可以用了,不过现在基本被弃用,因为它用起来很麻烦。让我们来看看它的属性。
string Error {get;}:这个属性有三个用途:
string this[string columnName] {get;}:这个索引器属性将返回属性特定的错误。
正如你所看到的,Error属性做的事情太多了,它将所有东西都拼凑成一个字符串,从而无法区分对象级别和属性级别的验证错误。如果你重新定义它,让它只包含对象级错误,那么就无法知道对象作为整体是否包含错误。
至于索引器,你会怎么调用它?要访问它的唯一方法是将该对象转换成IDataErrorInfovariable。然后,很少有人会期望看到这样的代码:
var nameError = ((IDataErrorInfo)customer)["Name"];
如果你的UI框架需要这个接口,我建议你将它放到一个基类中,并提供更合理的验证API。一旦加入真实的验证逻辑,甚至可以忽略IDataErrorInfo的存在。
INotifyDataErrorInfo的常规定义
我将分两次讨论INotifyDataErrorInfo接口。在本小节中,我将解释本该如何使用INotifyDataErrorInfo,然后在下一个小节解释我认为应该如何使用它。
INotifyDataErrorInfo接口旨在支持Silverlight 4中的异步验证,其基本想法是修改属性会触发服务调用,被调用的服务最终会结束并更新错误状态。
这个接口的唯一属性是bool HasErrors {get;},不过关于如何实现这个属性并没有硬性规定。我们有两个基本选项,但都不可行。
如果只是进行一般的显示,只要在发生EventHandler
此外,ErrorsChanged理论上可以触发两次:一次是立即触发,另一次是异步验证完成后触发。这可能会产生奇怪的UI效果,因为HasErrors会在两种状态之间切换。
最后是IEnumerable GetErrors(string propertyName)方法,这个方法用于验证属性。不过,你也可以传给它一个null或空字符串来获取对象级验证错误。
它返回的是IEnumerable而不是IEnumerable
不过缺乏类型安全并不是唯一的问题,这段话摘自它的文档:
此方法返回一个IEnumerable,在异步验证完成处理之前,可能会发生变化。绑定引擎因此能够在添加、删除或修改错误时自动更新用户界面验证反馈。
如果这个方法返回一个IObservable,或许就没有问题。但是在这种情况下,IEnumerable能够奏效的唯一方法是让它在等待异步验证完成之前阻塞。这样仍然会导致UI挂起。
然后是封装问题。如前所述,数据模型应该完全没有任何外部依赖。属性变化不应直接调用服务,因为这会使该类变得非常难以测试。如果你需要异步验证某些内容,请在控制器或视图模型中执行此操作。
INotifyDataErrorInfo的正确用法
尽管存在缺陷,但INotifyDataErrorInfo已经被用在很多UI框架中,所以我们无法忽略它。所幸的是,我们可以在不破坏兼容性的情况下重新定义它。
HasErrors属性可以在其他属性发生变化时进行同步更新。如果一个类实现了INotifyPropertyChanged,并且值发生变化,就会触发PropertyChanged事件。
不管指定的属性是有效还是无效,都应该触发ErrorsChanged事件。如果对象级验证已经发生变化,则应使用null或字符串触发ErrorsChanged事件。
在新模型中,GetErrors应该始终返回一个支持IEnumerable
基于属性的验证
我们可以使用基于属性的验证完成很多工作,虽然这样并不适合所有的情况。方法是在属性上放置ValidationAttribute的子类。这里有些例子:
要创建自己的验证属性类,只需重写IsValid方法。通常这用于单属性验证,不过也可以通过ValidationContext来访问对象的其他属性。
基于属性的验证的一个优点是,一些框架(比如ASP.NET MVC/WebAPI)已经选定它作为验证接口。因为它是声明式的,所以可以与UI共享验证逻辑。
混合命令式和基于属性的验证
虽然理论上可以使用验证属性来完成所有工作,但有时候使用普通代码可以更容易地实现严格的验证。这样做的原因如下:
命令式验证的一个缺点是它只存在于服务器端,无法像使用基于属性的验证一样自动与UI共享验证逻辑。
命令式验证的另一个限制是它需要使用共享接口,这样才能让应用程序的其余部分通过一致的方式触发验证。
空表单问题
当用户在创建新记录并未填写所有必填字段时,就会出现空表单问题。在显示表单时,你不希望看到每个字段都以红色突出显示。
为了解决这个问题,需要为模型提供两个额外的方法:
对于这种模型,模型对象将从初始状态开始。如果它在显示给用户之前已经包含了部分值,则应该在向用户显示之前调用清除错误的方法。
当用户修改某个字段时,只验证该字段。然后,在保存之前,可以调用验证方法强制对模型进行全面检查,包括非用户修改的属性。
理论上的验证接口
我认为.NET的验证接口应该看起来像这样:
public interface IValidatable { /// This forces the object to be completely revalidated. bool Validate(); /// Clears the error collections and the HasErrors property void ClearErrors(); /// Returns True if there are any errors. bool HasErrors { get; } /// Returns a collection of object-level errors. ReadOnlyCollectionGetErrors(); /// Returns a collection of property-level errors. ReadOnlyCollection GetErrors(string propertyName); /// Returns a collection of all errors (object and property level). ReadOnlyCollection GetAllErrors(); /// Raised when the errors collection has changed. event EventHandler ErrorsChanged; }
另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。