域实体应该作为接口还是作为普通对象公开?

时间:2010-02-28 21:00:55

标签: c# domain-driven-design

域实体应该作为接口还是作为普通对象公开?

用户界面:

public interface IUser
{
    string FirstName { get; set; }
    string LastName { get; set; }
    string Email { get; set; }
    Role Role { get; set; }
}

用户实现(在LinqToSql数据访问层中实现):

public class User : IUser
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public Role Role { get; set; }
}

用户实现(实现到NHibernate数据访问层):

[NHibernate.Mapping.Attributes.Class]
public class User : IUser
{
    [NHibernate.Mapping.Attributes.Property]
    public string FirstName { get; set; }

    [NHibernate.Mapping.Attributes.Property]
    public string LastName { get; set; }

    [NHibernate.Mapping.Attributes.Property]
    public string Email { get; set; }

    [NHibernate.Mapping.Attributes.Property]
    public Role Role { get; set; }
}

这只说明了一些DAL特定的实现,目前没有更好的样本。

4 个答案:

答案 0 :(得分:27)

我对此的感觉是域对象(不是域实体,因为该标题暗示与数据库有关)不应该是接口,除非你有一个非常令人信服的理由相信你会需要在将来的某个时候支持多种实现。

考虑域模型是人体模型。业务/服务/文档实际上是域。我们大多数人都在为单一业务或目的开发软件。如果域模型发生更改,那是因为业务规则已更改,因此旧域模型不再有效 - 没有理由保留旧域模型,与新模型一起运行。

辩论显然不是黑白分明的。您可能正在开发在多个客户端站点上大量定制的软件。您可能确实需要同时实现不同的业务规则集,同时真正需要将它们融入统一的体系结构中。但是,根据我的经验,至少,这些案例是例外而不是规则,虽然我一般不喜欢这个词,但这可能是你应该自己思考的情况,YAGNI

数据访问是您希望获得更好抽象的常见区域(persistence ignorance)。在您的示例中,您的模型类具有NHibernate属性。但是添加持久性属性使它不再是真正的域类,因为它引入了对NHibernate的依赖。 NHibernate和Fluent NHibernate支持使用外部映射声明而不是数据类上的属性映射POCO,这在使用NHM或NH4等ORM时往往是首选方法,因为它打破了持久性模型和域模型之间的依赖关系。 / p>

如果不支持这些映射方法,并且 使用属性,那么我可能确实建议使用接口,但今天的ORM比使用反射和动态代理更复杂。方法拦截来完成大部分繁重的工作,所以你不需要在这里创建自己的抽象。

希望作为接口公开的某些类型的对象是:

  • 存储库,负责加载/保存域对象;
  • 程序的插件/扩展程序;
  • 查看/演示者模型,以便插入不同的用户界面;
  • 具有许多实现的抽象数据类型(数组,列表,字典,记录集和数据表都是序列AKA IEnumerable);
  • 抽象操作有许多可能的算法(排序,搜索,比较);
  • 通信模型(通过TCP / IP进行相同的操作,命名管道,RS-232);
  • 任何特定于平台的内容,如果您计划部署到多个(Mac / Windows / * nix)。

这绝不是一个完整的清单,但它应该阐明这里的基本原则,即最适合界面抽象的东西是:

  1. 取决于可能无法控制的因素;
  2. 将来可能会发生变化;和
  3. 是横向功能(在应用程序/架构的许多部分中使用)。
  4. 域类将被广泛使用,但不适用于前两个类别中的任何一个;它不太可能改变,你几乎可以完全控制设计。因此,除非类本身将采用间接依赖(这是您应尽可能避免的情况),否则我不会为域模型中的每个类创建一个接口。

答案 1 :(得分:7)

接口通常被认为是“合同”,因此会定义行为。另一个主要用途是进行模拟,这样您就可以提供模型化的域实体,而不是来自特定数据源(并且依赖于该源)。

对于一个简单的数据传输对象,我看不到很多用于定义接口的东西,但我愿意被证明是错误的。我会选择简单的对象。

答案 2 :(得分:2)

在大多数情况下,不应键入实体。

实体明确定义了对同一类型的其他实体唯一的标识。 Order实体应该已经具有与系统中的所有其他Order实体不同的标识。如果您通过继承对其进行建模,它也可能会破坏实体的身份。

例如:假设您有Customer并且CustomerAmericanCustomer。把它作为美国客户所需要的所有行为和状态(提示:喜欢购物!)。后来同样的Customer,同名,相同的购物习惯 - 前往日本。他们还是AmericanCustomer吗?您是否创建了新的JapaneseCustomer并将包含该客户ID的所有数据复制到此新类型中?这可能会产生影响..

同一位顾客是否还喜欢购物?对于JapaneseCustomer,这可能是也可能不是。然而现在,在一次预订的航班中突然出现Customer不同。系统中的其他对象将使用其唯一ID请求相同的Customer,并且可能最终得到对象的不同图片(AmericanCustomer)。也许客户是以国家而不是当前位置为模型的。这可以解决一些问题,但也会引入新问题。

围绕其身份为您的实体建模。身份不仅仅与ID字段有关。一个模型不止于此。实体应该以业务逻辑的形式具有有意义的行为,是的,但它们不是服务。您不应该担心多态调度到不同的行为。更重要的是您的系统如何通过其唯一标识查看此实体。避免考虑实体类型。围绕身份进行组织。

答案 3 :(得分:1)

普通对象,除非有可以更改的实现的公共接口。这就是接口的用途。像Money或Address这样的值类不属于该类别。

除了getter / setter之外,您的示例界面没有任何行为。这不是接口的用途。