原文: EvansClassification 设计 Bliki 索引
下边是Eric Evans在他的杰作《 领域驱动设计( Domain Driven Design)》中开创的一套针对Domain Objects的分类法,在你的工作中很可能会遇到这些不同分类的Domain Objects。
- Entity: 在不同时刻不同表现形式下具有唯一身份标识的Object,也被人们称为“reference objects”。
- Value Object: 由属性值组合决定身份的Object。如果两个Value Object的属性值都相同,则它们被视为相等。我在《企业应用架构模式(P of EAA)》里也描述了Value Object。
- Service: 在业务领域上下文中的一个独立操作。把多个Service集中到一个object中就是一个Service Object。典型情况下,在执行上下文中每种Service Object类型只需要一个实例。
这套分类法很能引起我的共鸣,根据我的经验,它们正是你在领域模型(Domain Model)里所需要的。但麻烦的是很难把这三者界定清楚,它们是那种“你见了真人才认出是谁”的分类。
或许举几个例子有助于理解:Entity通常是Customer、Ship、Rental Agreement(租赁协议)这类大物件;Value通常是Date、Money、Database Query这种小东西;而需要访问Database Connection、Messaging Gateway、Repository或Product Factory这类外部资源的一般属于Service。
Entity和Value有一个明显的区别:Value会覆写(override)相等比较和哈希方法 (译注1)而Entity 不覆写。这是因为,在业务处理上下文中的实体,如果概念上相同,则最多只能构造出一个object来表示它;但如果表示的是像“5.0”这样的数值,构造出多个object也无妨。Value可以是原语类型(primitives)——在那些区分原语类型和非原语类型的语言里,或者由语言提供专门支持(像.NET那样),但这两点都不是必然的。Value Object应该是不可修改的 (译注2),这是一条重要的设计规则,违反的话容易身陷别名混淆bug导致的种种麻烦 (译注3)。要修改一个Value Object,比如一个代表身高的object,不能修改它的值,而是要构造一个新object。
Service Object经常用作全局变量、class级字段(Robert Martin称之为“monostate” (译注4))或singleton。通常它们都是单一的,只能获得唯一实例,如何实现这一点有多种方式。这种单一性往往是在业务处理上下文中的单一,在多线程环境下常是每个线程占有一个实例。不管具体是什么情况,都应该把实现机制隐藏起来,不让domain objects知道,这样可以轻松地改变具体怎么实现。Eric在他的书里讲Service应该是无状态的,我和他讨论过这个问题,他现在也认为这不是必须的了,不过能实现无状态更好。
做这种分类有一个麻烦——选用的术语与其他概念混得乱七八糟。Entity也经常用来表示数据库里的一张表,或是对应数据库表的一个object;Service与SOA(Service Oriented Architecture)还有应用架构里的 Service层混在了一起。因此,如果我使用这几个术语,我会先明确我是根据Eric书里的定义来说Domain Model的。听到别人用这几个词时一定要当心,它们被重载(overloaded)过度了,不幸的是很难找出替代者。
译注1:Java里为equals和hashCode。
译注2:immutable,请参考Joshua Bloch的《Effective Java Programming Language Guide》Item 13。
译注3:我理解的是:多个变量引用同一个非immutable object时,这个object就有了多个别名,通过一个别名修改,导致其他别名引用的值也修改了,而后者并不希望被修改。
译注4:请参考Uncle Bob的《敏捷软件开发——原则、模式与实践》16.2。