2010-02-28 33 views
29

应该将域实体作为接口还是作为普通对象公开?域应该以接口还是普通对象的形式公开?

的用户界面:

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特定的实现,没有更好的样品在这个时候。

回答

25

我对此的看法是域名对象(不是域名实体,因为该标题暗示与数据库有关)应该不是接口,除非您有一个非常有说服力的理由相信您需要支持多个在未来某个时候的实现。

考虑到域模型是人类模型。事实上,商业/服务/文档就是域名。我们大多数人都在为单一业务或目的开发软件。如果域模型发生变化,那是因为业务规则发生了变化,因此旧域模型不再有效 - 没有理由保留旧域模式,与新域模式一起运行。

辩论显然不是黑白的。您可能正在开发在多个客户站点大量定制的软件。您可能确实需要同时实施不同的业务规则集,同时也需要将其纳入统一架构中。但根据我的经验,至少这些情况是例外情况,而不是规则,虽然我通常不喜欢这个词,但这可能是一种情况,您应该自己想想,YAGNI

数据访问是一个您想要更好抽象的常见领域(persistence ignorance)。在你的例子中,你的模型类有NHibernate属性。但是添加持久性属性使其不再是真正的域类,因为它引入了对NHibernate的依赖。 NHibernate和Fluent NHibernate支持使用外部映射声明来映射POCO,而不是使用数据类的属性,并且这往往是使用ORM(例如NHibernate或EF4)时的首选方法,因为它打破了持久性模型和域模型之间的依赖关系。

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

某些类型的对象,你会公开为接口:

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

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

  1. 取决于有可能的因素超出你的控制;
  2. 未来可能会发生变化;和
  3. 是水平功能(用于应用程序/体系结构的许多部分)。

域类将被广泛使用,但不适合前两类;它不可能改变,并且您几乎可以完全控制设计。因此,除非类本身会承担间接依赖(这是一种你应该尽量避免的情况),否则我不会为领域模型中的每个类创建一个接口。

+0

@Aaronaught:好的ORM具体,现在假设我使用FullText功能,我会使用Lucene.NET也使用属性。据我所知,没有办法像FluentNhibernate那样使用外部属性映射。那么,为了让我的域名实体成为真正的POCO,最好的方法是什么?也许接口以这种方式工作? –

+0

@Yannn。 B:我不知道Lucene.NET完全使用装饰器属性;它绝对不需要它们。如果你想使用它们,那么你有两种选择:(a)让你的领域模型依赖于Lucene.NET,这似乎是一个坏主意(如果你想使用SQL FTS呢?如果将来的版本是Lucene.NET确实支持POCOs?),或者(b)将你的搜索对象和存储库移动到不同的命名空间/程序集,并使用像AutoMapper这样的工具将它们转换为域对象。无论哪种方式,我不认为为对象创建接口会有很大帮助。 – Aaronaught

+0

@Aaronaught:你说Lucene.NET不需要属性?它关于我发布的另一个问题(http://stackoverflow.com/questions/2356593/lucene-net-and-poco-entities),但你怎么做,而不使用Lucene.NET的属性? –

1

普通对象,除非可以更改的实现的通用接口。这就是接口的用途。金钱或地址等价值类别不属于该类别。

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

+0

我认为这个特定的例子是一个域实体,而不是一个值对象,因为它标识了一个特定的用户。但这取决于使用情况。 –

+1

对我来说同样的结论 - 域实体或值对象,接口允许更改实现而不影响客户端。 – duffymo

6

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

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

1

大多数情况下不应输入实体。

一个实体明确定义了一个对同一类型的其他实体唯一的身份。订单实体应具有与系统中所有其他订单实体不同的身份。如果您通过继承进行建模,它也可能会损害实体的身份。

例如:假设您有Customer,并且您实施CustomerAmericanCustomer。把它作为美国顾客所需的所有行为和状态(暗示:喜欢购物!)。后来那个相同的名字,相同的购物习惯的Customer - 旅行到日本。他们仍然是AmericanCustomer?您是否创建了一个新的JapaneseCustomer并将包括该客户的ID在内的所有数据复制到此新类型中?这可能会产生后果。

那个同样的顾客还喜欢购物吗?对于JapaneseCustomer这可能会也可能不会。然而现在突然在一次预订航班中这个Customer是不同的。系统中的其他对象将使用其唯一标识请求相同的Customer,并可能以不同于其预期的对象图片(AmericanCustomer)。也许客户是围绕国家出发而不是当前位置建模的。这可以解决一些问题,但也会引入新的问题。

围绕自己的身份建模您的实体。身份不仅仅是关于ID字段。模型不止于此。是的,实体应该有商业逻辑形式的有意义的行为,但它们不是服务。你不应该担心多态分派到不同的行为。更重要的是您的系统如何通过其独特的身份来查看此实体。避免考虑实体类型。相反,组织身份。

相关问题