再谈值对象

值对象是领域模型中比较难以理解的部分,这也不难怪,因为我们已经习惯与数据库打交道了,习惯了 类-表 对应的关系。既然一个对象要保存到数据库里,那它不就是实体吗??
好的,首先,忘记数据库,忘记!

我们使用jdon上一个例子来说明。有个帖子说了,目前系统中有相册这么一个功能,这个相册会被许多用户对象所引用,那么这个“相册”应该怎么设计呢?banq的答案是:把相册设计成值对象。
Ok,很多人看了以后,觉得这个答案很好,可以未必真的完全理解里面的意义。换个场景,就未必能想出答案了。

所以,首先,忘记数据库。

我们来设计一个系统,没有数据库的系统。所有的数据都存在于内存中。你不用管持久化的问题。
这个系统的功能是用户可以拥有自己的相册,可以收藏别人的相册。所以,很显然,我们有两个实体对象:用户和相册。
Public class Users {}
Public class Photo{}

没错,相册是实体,因为它有唯一标示符。
现在我们继续设计,用户会拥有自己的相册:
Public class Users {
Private List<Photo> myPhotoList;
}
嗯,用户还拥有对别人相册的引用:
Public class Users {
Private List<Photo> myPhotoList;
Private List<Photo> otherPhotoList;
}
其中 myPhotoList 是对好多个 Photo对象的应用。你问我“好多个 Photo对象”在哪里?ok,“好多个 Photo对象”在这个例子里,不是在数据库里,是在内存里。
例如我们创建了一个 MemDB 类:

Public class MemDB {
List<Photo> photoList;
}
这个 photoList 就是“好多个 Photo对象”,而这个 MemDB 就是我们持久化实体的地方。
用户对于自己的相册,也就是 myPhotoList,应该可以修改,删除,甚至增加等等。而对myPhotoList里的对象的修改,应该如实地反映到 MemDB里的photoList里。所以,myPhotoList里的对象就是实体。

用于对于收藏他人的相册,也就是otherPhotoList,只能看,或许可以修改,但也是修改给自己看,这些修改不应该反映到MemDB里的photoList里。所以,otherPhotoList里的对象就是值对象!从实现方式来说,应该是Photo对象的拷贝,而不是引用。当然,如果系统有这么一个特殊要求,收藏别人的相册,也可以进行部分的修改,那么otherPhotoList就是实体!

我想大家一定明白了。

不过,回归到现实,我们必须使用数据库,对不?
所以,otherPhotoList也好,myPhotoList也好,在代码层面上来说,都是值对象。为什么?因为当你修改了myPhotoList里的对象,并不会立刻反映到持久层,关键是你是否调用了存储他们的方法。这也是 DDD 与目前的技术无法一致的一个方面。

虽然如此,在分析阶段我们也有必要分清楚实体和值对象,毕竟我们可以遵循一些原则,如值对象在大部分情况下是无需保存的等等。这对我们能够清晰的了解系统有很大的好处。

注:附件中的pdf带有颜色等格式,方便阅读。
attachment:


think_of_value_object.zip

写得很好,对脱离数据库阴影设计有好处。

我现在感觉:值对象是从实体中扣出来的,大多数实体中都有值对象,比如相册可以是一个实体,但是其一些属性可以合并成相册值对象,被其他类似相册的实体使用。

相册被不同的用户使用,这是一个业务选择的结果,不一定相册要设计为值对象,相册也可以为实体。

实体与值对象的最大区别就是:实体保持有对一个对象的引用,而值对象是拷贝后的结果。
这个“引用”,体现在关系数据库上,就是一个类应该保存表的主键,如果是直接在内存中操作,如上面的例子,就是对象的内存地址。

而值对象无论如何都不应该有这种“引用”的属性,它应该只是拷贝后的结果,我们只关心“它从何处来”,至于“到何处去”,“是否持久”,都不是值对象要做的事情。

所以,如果设计中发现一个对象需要持久化,那它就绝对不是值对象。
所以,之前jdon中有人还在讨论“值对象如何需要持久化怎么办”,就属于绝对错误的讨论。

2009年11月04日 14:23 "yananay"的内容

所以,如果设计中发现一个对象需要持久化,那它就绝对不是值对象。

可以这么说,不过很多事情有变通,如果值对象确实要存储,那么就把它装在一个实体模型中存储。