跳至主要内容

ImplDDD 阅读笔记 - Value Objects, Part 1

Values types that measure, quantify, or describe things are easier to create, test, use, optimize, and maintain. Domain model 中真正的 Value Objects 并不是业务领域中的一种物品 (thing),它是用于测量、量化或是描述物品的。例如,一个人的年龄,它并不是一件事物,而是用来量化这个人从出生到现在的年数。 如果设计正确,Value Objects 的实例在创建之后就可以被随意传递,开发者可以即刻忘记这类对象,无论它们的生命周期是长是短,更无需担心对象的值是否被修改。在解决 Ubiquitious Language 的时候,确定正在建模的 domain concept 是否适合 Value Objects 是第一要务。有这么几个标准可以用来辅助判断: + It measures, quantifies, or describes a thing in the domain. + It can be maintained as immutable. + It models a conceptual whole by composing related attributes as an integral unit. + It is completely replaceable when the measurement or description changes. + It can be compared with others using Value equality. + It supplies its collaborators with Side-Effect-Free Behavior. 在绝大多少情况下,Value Objects 都是 immutable 的,一旦它们被创建出来,就无法再修改它的值。在 Java 或是 C# 这样的语言中,开发者往往使用 constructor 来构造 Value Objects 的实例,传入的参数被用于确定该对象的全部状态。构造参数可能直接被保存为 Value Objects 的 attributes,也可能被用于推导 (derive) 出对象内的一个或是若干 attributes。 在构造之后的任何一个时间点,对 Value Objects 的方法调用均不会导致其状态的改变。基于你的品位,有时候可能会希望在 Value Objects 内保存对 Entity 的引用,这是挺危险的,因为一旦这个引用的 Entity 的状态发生了变化,这个 Value Object 也要相应的改变。这种做法破坏了 quality of immutability,只能理解为 Entity references held by Value types are used for the sake of compositional immutability, expressiveness, and convenience. 一个 Value Object 可能只有一个,或是几个,亦或是若干的 attributes,它们是相关的。每一个 attribute 都对 Value Objects 所描述的整体有重要的贡献。从 Value Objects 中抽取出任一 attribute 都无法独立的提供有凝聚力的描述。换而言之,这与简单的将一组 attributes 组装在一起是有区别的—— the grouping itself accomplishes little if the whole fails to adequately describe another thing in the model. 来看这样一个例子,为了描述物品的价值,可以建立一个 `ThingOfWorth` 的模型: public class ThingOfWorth { private String name; private BigDecimal amount; private String currency; } 这样的代码导致客户必须理解什么时候以及如何使用 `amount` 和 `currency`,因为它们无法构成一个完整的表述,而实际上“5000 美元”可以作为一个完整的表述,它包含了 5000 和 dollar 两个概念,任意抽取其中的一个属性出来,都无法具体地对概念进行描述: public final class MonetaryValue implements Serializable { private BigDecimal amount; private String Currency; public MonetaryValue(BigDecimal anAmount, String aCurrency) { this.setAmount(anAmount); this.setCurrency(aCurrency); } } 以上代码并非最完美的例子,它仍旧有改进的空间——例如,可以使用另一个 Value type `Currency` 来描述货币种类,而不要使用朴素的 `String` 类型。这样可以更充分的对货币种类这一概念进行描述。另外,还可以使用 _Factory_ 或是 _Builder_ 模式来构建这类对象。不过这些都可能使得这个例子偏离原本的目的—— Whole Value。 Because the wholeness of a concept in the domain is so important, the parent reference to a Value Object is not just an _attribute_. Rather, it is a _property_ of the containing parent object/thing in the model that holds a reference to it. public class ThingOfWorth { private ThingName name; private MonetaryValue worth; // ... } `ThingOfWorth` 就是上面提到的 containing object,它包含了一个类型是 `MonetaryValue`,名为 `worth` 的 property。这两个名字都只有在确立了 Bounded Context 及其 Ubiquitous Language 的前提下才可以确定。 关于 basic value types 还有一个值得一提的特点——某些编程语言(Ruby)允许开发者为基本类型扩展行为,有时候你可能会考虑利用这一特性来直接以双精度浮点数来表达 currency 这样的概念。这样一来如果需要实现不同货币单位的转换,直接patch现有的 `Double` 类型为其添加 `convertToCurrency(Currency aCurrency)` 方法。乍一看可能觉得这么做很酷,可是这未必是一个明智的做法。因为这使得与货币有关的特定操作被隐藏到了与编程语言通用目的有关的类型概念里,`Double` 类型并没有针对货币这一概念做出特别的表述,这种基本类型的“滥用”实际上也就是 Ubiquitious Language 的含糊不清,最终会导致领域建模的失败! 正是由于 Whole Value 这个概念,在使用 Value Object 的时候可以完整的替换 (replace) 以更新描述的对象。这就像使用基本类型来赋值那样,不应当部分更新 Value Object 因为这样破坏了 immutability 和 whole value 特性。如果使用 Value Object 来作为唯一标识 Entity 的 identifier,那么是不可以对这个 id property 进行替换的。

评论