C# API中的模型和它们的接口设计

本文详细讨论了C# API设计中涉及的各种模型和接口,包括数据模型、DTO、领域模型、实体和业务实体等。强调了模型的验证、不可变对象和数据访问层的重要性,并详细阐述了如IDataErrorInfo、INotifyDataErrorInfo等验证接口的使用和最佳实践。同时,文章提及了属性变更通知、类型安全的集合变更事件以及变更跟踪等关键概念,为C# API设计提供了深入的指导。
摘要由CSDN通过智能技术生成
\

关键要点

\\
  • 可变模型应该具备自我验证的能力,并实现验证接口。\\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的实体通常会包含一个对数据库上下文的引用。这就又让我们回到了非确定性行为的领域,实体行为的变化取决于上下文状态以及对象的创建方式。

\\

换句话说,数据模型的所有方法都应该是可预测的,而且这种预测只能基于它们的属性值。

\\\\

父对象和子对象通常需要交互。如果做得不好,可能会导致难以理解的紧密交叉耦合。为了简化问题,请遵循以下三条规则:

\\
  1. 父对象可以直接与子对象的属性和方法交互。\\t
  2. 子对象只能通过触发事件与父对象进行交互。\\t
  3. 对象不能直接与兄弟对象交互,兄弟对象之间的消息必须通过共同的父对象来传递。\

基于这样的设计,可以将子对象分解出来,并在没有父对象的情况下对其进行测试。测试本身可以监控只有父对象能够处理的事件。

\\\\

接下来我想谈谈数据模型可能会实现的可选特性。但在开始之前,我想先讨论每个数据模型必须具备的一个特性:验证。

\\

完全不处理数据的数据模型几乎是不存在的。如果模型是来自文件、外部应用程序或用户界面,就有可能会引入不一致或不合法的值。来自用户界面的问题会更多,因为用户通常需要逐个字段得填写表单。

\\

因为存在这些限制,所以不能在构造函数和属性设置器中使用异常,就像你在其他类中使用异常一样。不过可以验证接口,为错误检查提供一些灵活性。

\\

.NET提供了一些开箱即用的验证接口,不过每个人都有自己特定的需求。

\\

IDataErrorInfo

\\

IDataErrorInfo接口早就可以用了,不过现在基本被弃用,因为它用起来很麻烦。让我们来看看它的属性。

\\

string Error {get;}:这个属性有三个用途:

\\
  • 报告对象级别的错误\\t
  • 报告所有属性级别的错误\\t
  • 通过返回一个空字符串来表示不存在错误\

string this[string columnName] {get;}:这个索引器属性将返回属性特定的错误。

\\

正如你所看到的,Error属性做的事情太多了,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值