我熟悉DDD和实体的概念。
根据DDD,实体是一个基本上由其身份定义的对象。
说,在我的项目中,我发现Account
是一个实体。所以在代码中,这将由具有标识符字段的类表示,类似于
class Account {
private Id id
private AccountStatus status
...
}
由于这是一个实体,因此除了Id
之外,其所有字段的生命周期都会发生变化。
我的问题是,在状态变化和对象相等方面对代码进行建模的最佳方法是什么。
由于实体的状态随时间而变化,是否应将类建模为可变类,还是应该使用每次状态更改创建的新引用进行不可变?
由于Account
仅基于其Id
标识,因此equals方法是否应仅比较对象的标识符?考虑所有 OR 无字段会有什么潜在问题。
答案 0 :(得分:1)
我建议你复习Clojure的时代时间模型。 Stuart Halloway的2010年演讲Perception and Action详细介绍了epochal time model。
在某种意义上,实体是对不可变状态的可变引用。
在面向对象的风格中,我们可能会做类似
的事情Entity {
MutableRef<State> mutableRef;
void change (...) {
State current = mutableRef.get()
State next = someFunction(current, ...)
mutableRef.set(next)
}
}
(我们通常不会这样做,因为在OO的Java谱系中,通常的做法是操纵可变值。)
我的问题是,在代码中对此进行建模的最佳方法是什么?我看到两种方法,
由于这是一个实体,从概念上讲,其所有字段都可以更改,因此模型应该支持仅在Id字段上定义的变异(setter)和相等。
将此模型作为值对象,即不可变的最终字段,在所有字段和变异上定义的相等,创建并返回一个新对象。
如果您使用业务语言将帐户描述为随时间变化的事物,则可能需要使用实体模型。
这并不一定意味着&#34; setters&#34 ;;更常见的风格是,mutators应该用业务语言编写。我们告诉实体该做什么
account.close()
及其实体的工作,以了解该操作如何影响基础数据结构。
至于身份,这是Evans最近的一句话:
我开始相信一个实体甚至不应该有一个相等的操作
视频片段似乎取自Julie Lerman和Steve Smith的2014 Pluralsight课程Domain-Driven Design Fundamentals模块。
答案 1 :(得分:0)
通常,Entities和ValueObjects是给定案例的互斥概念。不同的域也对不同的概念有不同的关注,其中一个概念可能是给定域中的ValueObject和另一个给定域中的实体。
一个这样的例子是Money作为一个概念。对于电子商务上下文,Money可能是一个ValueObject,因为域只关心它的值而不是它的持久身份(对于这样的系统,2个10美元的实例将是相同的,你不会关心它们分开) 。
对于联邦银行(或任何打印货币的实体),以完全不同的方式对待货币。这个域实际上用序列号标记单个账单,并且非常关心每个账单的历史记录(何时打印,这是模型,可能也是打印的位置)。因此,该域名可能会将Money建模为实体。
这些差异也会影响平等处理方式。
对于ValueObject,通常在“具有相同值的2个实例”上定义相等性,因此,您可能更好地匹配每个值字段以建立相等性。 Money实例“10 USD”等于另一个Money实例“10 USD”,但它与Money实例“10 EUR”不同(他们的FaceValue属性是相同的,但他们的货币不是这样你不能使用它们互换)。
现在,对于一个实体,您关心给定实例的整个生命周期的身份(作为一个概念,而不是实例化)。因此,如果您有一个序列号为1234且属性为“10 / USD / NearMint”的Money实例并且它已损坏,则会更改并保留其序列号1234,但其属性将更新为“10 / USD / Stained”。它仍然是相同的法案,并且对于所有帐户,它应该对平等比较器作出回应。
只是为了完成这个长答案,“setter”部分也依赖于域。通常,ValueObjects不应更改其内部状态。但实体应该只根据域规则更改其内部状态。在钱的例子中,即使它是一个实体,也许完全没有理由能够获得给定账单的FaceValue或Currency。然而,它的状态NearMint - &gt;染色 - &gt;损坏 - &gt;等等可能会随着时间的推移而发展。此外,您应该考虑不使用始终直接的setter,而是在实体中创建域有意义的方法,以便在处理状态转换时更好地表达无处不在的语言。