一般情况下,getter/setter是针对实体的单个属性,而对值对象的getter/setter是针对实体的多个属性。
为什么要对实体的属性进行分组,这要考虑实体的哪些属性是否有强烈的关联,是否可以凝聚在一起成为一个完整、相对独立于实体的概念。

《实现模式》上有一个例子,可拿来说明一下为什么可能需要值对象?

比如金融票据的数值与币种会同时变化,那么这两个字段最好放到一个辅助对象Money中:


setAmount(int value, String currency) {
this.value = value;
this.currency = currency;
}

上面这段代码就变成了:


setAmount(int value, String currency) {
this.value = new Money(value, currency);
}

然后进一步调整:


setAmount(Money value) {
this.value = value;
}

值对象是DDD(广义上,是一种设计模式)的一个概念,在分析模式中对应的概念是四色原型中的DESC,在实现模式中对应的是变化率实现原则,即把具有相同变化率的逻辑、数据放在一起,把具有不同变化率的逻辑、数据分离。

值对象数据一致性,是变化率原则的必然结果;不变性,则看值对象需要共享。

《领域驱动设计》这本书,行文风格是深入深出,没有做到平易近人、浅显易懂。四色原型(彩色UML)和实现模式那两本书还写得浅显一些。一个概念,最好能从分析、设计、实现三个维度,去理解它,这样可能容易些摆脱思维的困境。

2011年07月27日 09:52 "@jdon007"的内容
而对值对象的getter/setter是针对实体的多个属性。
为什么要对实体的属性进行分组,这要考虑实体的哪些属性是否有强烈的关联,是否可以凝聚在一起成为一个完整、相对独立于实体的概念。 ...

这里,可以看出我理解的值对象跟你理解完全不一样。从“属性分组”可以看出,你是把值对象看作变量集合,而我是看作变量的值。变量集合并没有值对象的确定性。值对象的内部属性的值,一定是已确定的。

属性分组并没有考虑值的合法性。而值对象包含合法性存在,即成为了某种值对象,则受该种的规则所约束。如你说的金融票据,数量不能为负等。注意,这种约束不是对赋值约束,而是存在约束(生成约束)。

举个例子:
Class Value{
A a;
B b;
Value(A a,B b){
this.a = a;
this.b = b;
}
//get自行补完,没有set
}

Value vo = new Value(1,2);
这里先假设a的有效可能值1,3,5等,b的有效可能值是2,4,6。(此就是该值对象的存在约束)。那么值对象就是ab的笛卡尔积,即new Value(1,2),new Value(1,4),new Value(1,6)……,而不是 new Value(a,b)。用二元组表示就是(1,2),(1,4),(1,6)……,而不是(a,b)。

2011年07月27日 09:52 "@jdon007"的内容
值对象是DDD(广义上,是一种设计模式)的一个概念,在分析模式中对应的概念是四色原型中的DESC,在实现模式中对应的是变化率实现原则,即把具有相同变化率的逻辑、数据放在一起,把具有不同变化率的逻辑、数据分离。 ...

我并不赞同相同变化率来划分(我指的是属性变量划分,而不是值对象划分),的确,某些时候相同变化率可以很好发现。但这种做法却远离了值对象本意,因为无论值对象还是变量,本身就带有一组完整的概念,从这概念入手即可。金融票据,本身就带有一组概念,而不是因为相同的变化率,而创造出来的东西。

2011年07月27日 09:52 "@jdon007"的内容
而对值对象的值对象数据一致性,是变化率原则的必然结果;不变性,则看值对象需要共享。 ...

从上述观点,不变性,是其本质特性,可变是一种妥协,说好听点就是“技巧”。
变化率是实体的部分属性“凝聚”的外在特征,是看得见的,是将其识别出来“值对象”概念的基本手段。

书上说得很清楚呀,不变性只是极力推荐而已,绝非本质,我试着解释一下。

客户的“地址”可以设计成“值对象”,乘客的“航线”也可以设计成“值对象”,而后,客户或乘客持有值对象的引用。

客户张三和李四的住址不同,各自都搬家了,两个人的地址都变了,这个地址值对象,能不让变吗?你搬你的家,我搬我的家。
张三和李四的地址不需要共享,也就是说此时“地址值对象”最好设计成可以修改的。

乘客张三和李四都要去北京,张三临时换乘去香港的航线,可是航线这个值对象,能让变吗?李四可不想去香港。此时张三不是去改变值对象,而是去选择另一个值对象。此时“航线值对象”的设计最好是只读的,因为航线是共享的。

但是不论值对象是否需要共享(不变性),数据一致性(与事务上的一致性概念相似)都是必要的,为什么,因为它们在概念上是一体的。

Kent Beck在《实现模式》举的金融票据的例子,是从“一致性”的角度考虑的。我认为值对象的“一致性”(或概念上的原子性)才是其基本特征(值对象诞生的根本原因),“不变性”则看是否有共享的需求。至于值对象的值的约束,这是概念本身决定的,比如金融票据的币种,就是$、¥等有限的种类,数值嘛,要限制当然也可以,比如ATM上,只能取整佰,而且只有几个选择。上面的Money这个值对象是可以修改(setter)的。

2011年07月27日 16:17 "@jdon007"的内容
客户的“地址”可以设计成“值对象”,乘客的“航线”也可以设计成“值对象”,而后,客户或乘客持有值对象的引用。 ...

这个就是我和你的根本出入点,地址不是值对象,而是变量,“北京”才是值对象。我们无论如何划分变量,最终还是得出变量,只是把个体变量变成整体变量。“给值对象赋值”我认为是错误的,值对象本来就是值以对象方式存在的(或者说它是完备值的概念的对象),它是被赋得值,也就是本身是一个完整的定义。当我们new Address("北京"),只需要指定这一对象就是指“北京”。若果new Address("北京").set("上海")变成“上海”了,则完全违背“同一对象,就是指同一物”这一概念。
对于共享,我提出:值对象本身就是为共享而存在,也是值意义所在。“北京”无论怎么用,无论放在什么地方,它就是指“北京”,“北京”不会变成“上海”的意思。

书中对Value Object的一些描述:

1) Many objects have no conceptual identity. These objects describe some characteristic of a thing.

2)When you care only about the attributes of an element of the model, classify it as a VALUE OBJECT.

3) VALUE OBJECTS are instantiated to represent elements of the design that we care about only for what they are, not who or which they are.

4)A VALUE OBJECT can give information about an ENTITY. It should be conceptually whole.

5) To protect against this, in order for an object to be shared safely, it must be immutable: it cannot be changed except by full replacement.

6) Immutable:The property of never changing observable state after creation.

值对象的含义:描述事物的一些特征,比如颜色、尺寸、金额等,相对独立于实体的一些概念,没有标识。没有不变性的含义,甚至没有一致性的含义,一致性设计在符合“变化率原则”时才需要。

不变性的含义:1)创建之后属性不能修改;2)对象只能整体替换,不能局部修改。

值对象产生的原因:事物的一些特征具有极强的凝聚性(相似的变化率),比如Kent Beck例子中的金额包含的value和currency属性,此时就要从票据这个实体中剥离出Money这个值对象。凝聚性,表示值对象概念具有一定的原子性(根据不同的业务需求,原子粒度可能会有所变化),修改时最好整体替换。

创建之后属性不能修改才是“不变性”的含义;对象只能整体变更,不能局部修改,这实际上是“一致性”的含义,是值对象理想的特性,要称作“不变性”也无妨,如果大家都这么说的话。

实际在jivejdon中,值对象的设计也没有完全完全遵行“一致性”(或“不变性”)的原则,看看Forum.java
ForumState.java(值对象)就可以知道了。ForumState从Forum分离出来的属性的变化率不一定完全一致,只是可能存在一致的情景,所以没有完全遵循值对象一致性或不变性的设计。

2011年07月27日 22:26 "@jdon007"的内容
看看Forum.java和
ForumState.java(值对象)就可以知道了。ForumState从Forum分离出来的属性的变化率不一定完全一致,只是可能存在一致的情景,所以没有完全遵循值对象一致性或不变性的设计。 ...

同意你的独立思考,我现在也认为ForumState不是值对象了,如果是同一种性质的状态可以作为值对象。

2011年07月27日 22:26 "@jdon007"的内容
描述事物的一些特征,比如颜色、尺寸、金额等,相对独立于实体的一些概念,没有标识。 ...

值对象不是描述有什么特征,而是说该特征如何。在“颜色=红”式子中,值对象是“红”。之前看过说跟常量,枚举差不多。其实也可以这么认为,但常量,枚举是有限的,而且不是从约束条件出发,明显带有局限性。值对象只需要符合存在约束,就可以成立。而实现上的区别是,常量和枚举必须是编译时期确定,而值对象根据存在约束就可以在运行时确定,可以理解为相对实时的状态。

你所提到的“一致性”就是我说到的存在约束。但跟“不变性”是两回事,“不变性”来自下面几个地方:

1、值对象是描述一个瞬时状态,它不是用来描述过程。也就是说众多值对象组成的是一个离散关注的状态集。

2、值对象是整体概念,哪怕只是一丁点不同,就是不同的值对象;而且不能独立观察,不能说只是当中的某个值变了,而是说整个都变了。如Color(红,绿,蓝)变量集合,值对象Color(255,255,255)给出的信息就是白色,Color(255,255,0)给出的信息是黄色,油漆使东西从白色变黄色,即color = Color(255,255,255);变成color = Color(255,255,0);而不是说当中白色当中的“蓝”变了,所以颜色变成黄色,即color = Color(255,255,255).setBlue(0)。

white = Color(255,255,255);

yellow = white.setBlue(0);

white == yellow?
white.equal(yellow)?

3、受实体概念所约束,值对象代表有意义属性的具体状态,即本身整体就含有某种特殊意义,而不是分割看待。实体有颜色属性,则说明遵循该意义的值对象,就是颜色中一种,各种颜色相互独立

不变性的意义(自己的粗略总结),除了技术上说的共享,还有就是可使概念清晰(白色就白色,黄色就黄色),脱离没意义的代码;在实现实体的状态迁移时,不会使实体存在没意义的时刻;没意义时刻脱离实体,使读锁更低频率出现;而同时值对象一直是处于有意义状态,只要获取到值对象都是有意义,不论任何时刻……其实还有不少意义,只是我也没那个时间去全局总结。


后注:

没意义时刻:
Color(255,255,255)向Color(0,0,0)转变

color = Color(255,255,255);

color.setRed(0);//color处于没意义时刻
color.setGreen(0);//color处于没意义时刻

color.setBlue(0);//改变完后变成有意义

没意义时刻,不分共享不共享,若果共享的时候也存在没意义时刻,就会带来共享问题。而这种没意义时刻,是一种过程化思想的体现。
[该贴被SpeedVan于2011-07-28 10:39修改过]

Value Object 相对于DESC, 其内涵已经少了很多,若对Value Object再加上“一致性”与“不变性”的约束,其存在价值就更少得可怜了。

显然我对值对象的理解,比你的定义要广泛得多。

一般的值对象,是因为其属性具有极强的关联性,在概念上具有独立性与完整性,这点是共识,这很好。
一致性的值对象,是因为其属性具有相似变化率,比如Color就是可以设计为一致性值对象的绝佳例子。
不变性的值对象,是因为其属性具有恒定性,也就是其变化率为零时,比如万有引力常数、光速等,是一个常量集或枚举类(与在编译时还是运行时没啥关系,是因为概念具有不变性)。事物某一个时刻的状态值,可以理解为不变性的值对象,因为它的变化率也为零(从一个孤立的时间点来看,啥东西变化率都是为零)。

广泛得多,是因为你把我提出的另外一种概念(变量集合)归纳到值对象里面。

变量集合与值(值对象)的区别在于:

变量集合的值是不确定的,也就是它是描述了有什么特征,但没有描述是什么样的特征。从这点来看,已经跟值(值对象)相悖了——它没有代表领域中某种描述性特征,它不具有描述能力。

关于不变性,我之前就说过,这是来源于值概念。1就是1,“上帝”就是“上帝”,(1,2)就是(1,2)。该数字是1,该人是上帝,该元组是(1,2),这里我们可以找出一条式子:A是B。可见B就是值,用于描述A(特征)。同样地值对象也一样。若果B是可变的,那么请问B描述了A什么?

在DDD一书中,Evans在谈论设计值对象时,已经把可变的值对象作为特殊情况,他已经觉得可变性对于值对象而言显得相当唐突,但他又不划出去,于是便加入“特殊情况”,而我觉得是他没有找出根本分歧点。在使用情况中,除了第3点,其他都是因为妥协而产生的,是一种偶然性。关于第3点,我不明白他所说到的前面例子是指哪个例子,若果是说副本的,我也只能说副本与不变性没有任何冲突。还有我认为Evans谈论值对象时“值”得不彻底——值对象不应该包含实体,而是包含着实体的信息。实体是可变的,于是值对象也变成可变了,于是也就变成之前的问题了。Route表达了一条路线,要看其是实体还是值对象,当以实体的形式时,则可以保存实体,当以值对象存在时,则不可以存实体,存的是实体的信息。而DDD一书中谈到,值对象可以存放实体,这就是我之前提到的(变量集合),两个城市,一条高速公路,至于是哪两个城市和哪条高速公路则不得而知了。若果你认为这条Route就是旧金山到洛杉矶,那么已经从Route(出发地,目的地,他们之间的路线)(变量集合)变成Route(旧金山,洛杉矶,I-5S)(值对象,是可确定的)。

所以我们明显的分歧点在,特殊情况,是否归入值对象。可以把我的观点看作对原DDD值对象的挑战吧——我不认为特殊状态属于值对象,根本原因变量集合,可变性使得其不拥有某种确定的意义,也就不具有描述能力,他只拥有数据装载能力。

两位讨论很精彩,我觉得是否从两个层面角度来看值对象:分析角度和软件实现阶段。

两位前者讨论比较多,后者在 java 中涉及到 引用和值 的问题,好像这是初学者问题,实际是取决于我们分析时对该对象性质判断,如它是值对象,我们就不能将引用传递给其他使用者,这实际是共享,而是直接克隆或复制等。

不变性,一致性在这里得到贯彻和保证。

还有值对象中每个字段都可以加上 final ,这也是保证其只读,不可内部被改的贯彻和保证。

scala 已经可以用语言来声明一个变量类型是否不变或可变,这会并发性能带来优化提升空间。

[该贴被admin于2011-07-28 20:31修改过]

将特征定义为不变的值对象太狭隘了,特征可以是变量,可以定义为一个时间函数。

比如,可以通过矩阵或特征向量空间来描述线性系统,而特征向量的每个分量都可以是一个状态变量x(t)。
如果你学过线性代数,可能会认为向量的每个分量都是一个常数,但事实上每个分量可以是一个时间函数,这才是特征向量的普遍意义。
再比如,使用I(t)描述一个电流, 也是一个时间函数,I(t)刻画了该电流的特征。

我说过从一个孤立的时间点来看,啥东西变化率都为零,啥东西都具有不变性。这跟这个东西是实体还是值对象没啥关系。

所谓的不变性,就是一个东西在领域的生命周期内任何时刻都一个样,是一个常量。

Evans之所以建议将航班路线,设计成不变性的值对象,是因为航班路线是比较稳定的,在领域的生命周期内,其变化率接近于零。

值对象因为在概念具备独立性从实体上剥离出来,这就是值对象(特征)的内涵,其它的都是外延,包含了(非)一致性和不变性的值对象。

我们的分歧不是特殊情况,是否归入值对象,而是你忽略时间,从一个(个)孤立的时间点看特征。如果你考虑了时间,考虑了变化率,考虑了特征在概念上的独立性,就不会认为不变性是值对象根本特征,不会认为特征变化是特例,不变才是特例。

至于定义时是否将字段设置为final,传递消息时是pass by copy,pass by referece, 还是pass by value,与值对象并无必然联系,选择哪一种实现手段,取决于对目标(一致性、容错性、有效性/可用性等指标)的考量。

我觉得从分析角度看,是否需要区分 which 是值对象主要标志,其他都是次要。

实体和值对象都属于结构型模型,在时间上有一些连续性,对时间不是很敏感, ,说白了是名词静物。 所以以时间为判断依据我认为偏离方向。

2011年07月28日 20:53 "@banq"的内容
说白了是名词静物 ...

VALUE OBJECTS are instantiated to represent elements of the design that we care about only for what they are, not who or which they are.

这是VALUE OBJECTS的定义,我很欣赏这个定义,这才是VALUE OBJECT的内涵,不变性与一致性都是其外延。这点上我与banq的看法完全一致。

只是SpeedVan一值强调不变性才是VALUE OBJECT的本质,所以我必须引入“时间”这个概念来解释清楚。

VALUE OBJECT实际上表示事物或系统的特征,特征普遍意义的数学描述是一组时间函数。这组时间函数具有相同的变化率时,特征就具备了一致性;这组时间函数是一个恒等变换式时,特征就具备了不变性(常数特性)。

banq说Value Object是名词“静”物,这点我不完全赞同,Value Object实际上是名词“动”物,从上述的VALUE OBJECT的数学描述就可以看出来。

言至于此,本无须再言,但banq是布道人,所以我有义务“纠正”(别介意这么用词)你的一些不当的观点。这些观点已经超出了这个帖子的范畴,是我对领域建模一些思考,所以放在我的帖子中去《领域驱动设计之我见》

值对象本身就是一个瞬时点。不存在过程变化,所有相关的值对象的描述,组成了变量的变化过程。

I = I(t)这式子只表达了I与t的变化规律,但没从根本上描述I是多少。当我们去取值时,是取一个变量的当前状态。我可以分析下你这个I(t)。I(t)理解为函数时,也就说明I代表的是一个变化规律,而非电流,而整个I(t)便当为值。I的变化规律 = I(t);但当I理解为电流时,则不能以I(t)为值,因为其不能描述电流的具体状况。A问:“这电流表显示的电流是多少?”,B答:“I(t)”,A:“!!!”。

I = I(t)还可以有另外一种理解,I和I(t)都共同指向一个值对象,I(t)本身不是值对象,但经过传参运算后,得到一个值对象,然后该值对象赋给变量I。但这时的t是一个已确定的值。

I = I(t);

I = I(60);
看起来很相似,但却代表着不同的意思。
I = I(t);的I意味着电流与时间变化规律,而非电流,规律变量 = 具体规律,这是符合变量与值的关系;
如公式集合U = {I(t),I2(t),I3(t)……};,公式变量I,A问:“请问该电流公式是什么?”B答:“I(t)”。这里非常明显地看出,此时的I(t)不是一个变化的存在,而是一个值,就是I(t)。考虑t的变化,已经不是I得范围了。

I = I(60);的I意味着电流,I(60)得出I规律在t=60时的值,把该值赋给I。

请理解,变量和值本身就有一种关系。在赋值之前,请确定变量的含义。

最简单的:

I = I(t);

println(I);

请问,输出是什么?

比如 I(t) = 2t+3;
print(I(t)), 打印出来的就是2t+3;
假设表示当前时刻t = 1
print(I(1)), 打印出来的就是 5, 隐含“当前时刻”的概念。

交流电(I(t) = 2t+3) 和直流电(I(t) = 5)的当前时刻的值都是5,5是特征?5是直流的特征,因为直流的特征是一个常量特征;但却不能表示交流电的特征,2t+3才是交流电的特征,5只能表示当前时刻交流电的值。一个值对象就是一个值(甚至一个常量向量)? 如果真是这样,值对象的概念就毫无意义,因为常数和枚举量比其更具有普适性。

说过N遍了,从孤立的时间点来看事物的特征,啥东西都具有不变性, 也没有太大的意义。

在没有时间的OO世界观,并行其实是就是状态共享,才会去强调值的不变性,但这根本不是值对象(特征)的本质,
这只是Immutable模式一个应用的例子而已,因为Immutable所以可以去锁,提高访问性能,但在有时间的OO世界观中,锁不是必须的,这只是一个特例,并行消息传递才是常态。
[该贴被jdon007于2011-07-29 10:48修改过]