像Pro一样珍惜物件
如何为您的领域构建完美的课程。
不久前,我在一个编程网站上被问到这个自动化问题:
您将使用哪种变量类型来代表一个人的年龄?
[ ] 整数
[]布尔
[]字符串
我微笑着离开了网站。
正确的答案不是三个 。 要代表一个人的年龄,您应该使用年龄类型。
“ 整数还不够吗? “ —您可能会问。 没有。
整数具有年龄没有的不同属性和运算。
- 加减两个年龄有意义吗? 也许。
- 将两个年龄相乘或相除有意义吗? 不要这样
- 允许否定年龄有意义吗? 可能不是。
一个简单的事实是,Age不是整数,因此不能这样表示 。
使用基本类型来表示域中对象的错误做法非常普遍,甚至有一个名称: 基本痴迷 。
但是,等等,我还没有完成整数。
如何确保代表年龄的整数变量使用有效值初始化? 在代码中的每次分配中,您都需要在分配之前明确检查。
以及如何确保您的整数值在以后不会被意外修改? 除非您使用的语言允许不可变的变量并且如此声明,否则您就不能这样做。
这里有足够多的观点说明原语不足以建模您域中的对象。
您需要满足域的精度及其语义细微差别的信息的不同原子单位。
不再搜索,因为该单元已经存在。 它称为值对象 。
值对象表示您域中的类型化值。 例如,年龄。
它具有三个基本特征:不变性,价值平等和自我验证。
使用基本类型来表示域中对象的错误做法非常普遍,甚至有一个名称: 基本痴迷 。
不变性
值对象是不可变的。
这意味着创建后无法更改其内部值。 不允许二传手 。
将一个或多个参数注入构造函数后,就没有回头路了。 该对象将保持不变,直到被垃圾收集器处置为止。
不变性带来两个巨大的优势:
1)轻松共享
您可以通过引用共享任何Value Object,因为它是不可变的,不会在代码的另一部分中进行修改。
这大大降低了避免引入任何错误所需的意外复杂性和认知负担。
特别是当您的代码将在多线程环境中运行时,这是黄金。
2)改进的语义
将不变性与另一条规则结合: 默认情况下,请勿将任何吸气剂添加到Value Object中 。
限制-我再说一次-限制添加任何方法都是因为“ 以后可能需要它 ”。
您的初始类应仅具有构造函数和一堆私有属性。
这使您可以在以后考虑数据的转换。 这意味着,当您了解Value Object的确切用例时,便可以决定方法的语义。
这样做可以避免无意义的接口,并为Value Object定义有意义的名称和行为,从而改善模型。
通常,这是您操作值对象的方式:
- 您可以通过构造函数或静态方法创建新实例;
- 您从中创建另一个值对象;
- 您提取内部数据,然后将其转换为其他类型。
强加的不变性迫使您用与您的领域相关的语言来表达这些情况,并且对于以后的代码读者(包括您自己)完全清楚。
让我们来看一个例子。
价值平等
想象一下,您和您的朋友从两个不同的扑克牌中各自选择一张牌,并且您想了解您是否选择了同一张牌。 你是怎样做的?
您可能要做的就是检查两张卡是否显示相同的编号,并且使用相同的西装。 换句话说,您检查它们是否具有相同的属性 。
想象一下,现在您交换了两张卡。 您从您的朋友那里拿走一个,您的朋友也从您那里拿走。 有什么改变吗? 不。您仍然有同一张卡,所以您的朋友也一样。 具有相同的属性,这两张卡彼此之间是无法区分的。
事实证明,这两张牌实际上是价值对象。
具有相同内部值的两个值对象被视为相等。 这意味着您可以通过检查内部值是否分别相等来测试是否相等。
Java开发人员注意:请不要使用equals和hashCode废话。
自我验证
值对象仅接受在其上下文中有意义的值。 这意味着不允许您使用无效值创建值对象。
当值对象注入到构造函数中时,必须检查其值的一致性。 如果其中一个值无效,则必须引发有意义的异常。
这意味着在对象的实例化周围不再有if 。 每次正式验证都在构建时进行。
这种强制验证对于以有意义和明确的方式表示域约束也很有用。
让我们来看一个例子。
摘要
总之,您是否要构建一个健壮,可表达且完全适合您的域用例的Value Object?
这是您必须始终对照的列表:
- 它是不可变的,未定义setter;
- 它反映了域的语义;
- 它显示了信息在运行期间如何流动和转换;
- 它没有默认或没有用的getter方法;
- 通过直接读取私有属性,可以将其与相同类的其他值对象进行比较。
如果遵循此清单,您的代码库将再也不会看起来相同 。
通读它甚至比读起来容易。 感觉很好。
所有这些仅仅是由于精心设计的Value Objects。
From: https://hackernoon.com/value-objects-like-a-pro-f1bfc1548c72