值对象设计经验

OOD,DDD又爱又恨。

什么是Value Object

值对象(Value Object)既是面向对象设计中的一个重要概念,也是领域驱动设计(Domain-Driven Design)中的一个重要概念。

它表示一个没有唯一标识符的对象,其身份由其所有属性决定。

换句话说,值对象是通过其属性值来区分的,而不是通过一个唯一ID。

举例说明:

  • 一个日期,如果两个日期对象的年、月、日属性相同,那么它们理应是相等的。
  • 一个货币金额,由数字部分和货币符号部分组成,如果两个金额对象的数字部分和货币符号部分相同,那么它们理应是相等的,例如100¥=100¥。

值对象的特点

  1. 不可变性。值对象不可改变,因为值本身不可改变。100就是100,100¥就是100¥,不可能改变成200¥。
  2. 可以进行比较,即便是两个不同值对象实例,只要它们的属性值相同,就认为它们是相等的。

值对象建模

  1. 优先使用Java中的record来定义值对象。record是Java 14引入的一种特殊类型,用于定义不可变的数据类,非常适合用来定义值对象。
public record Money(BigDecimal amount, String currency) {
    public Money {
        if (amount == null || currency == null) {
            throw new IllegalArgumentException("Amount and currency cannot be null");
        }
    }
}

record自动生成了构造函数、访问器方法、equals()、hashCode()和toString()方法。

  1. 如果还在Java 8,可以使用final普通类来定义值对象,确保类不可变。

值对象设计经验

  1. 识别可建模的值对象

一般在业务需求分析和概要设计时期,可以识别到业务模型中一些概念封闭、具有编码解码特征的属性,这些适宜建模成值对象。例如:日期、时间、货币金额、时区、语言代码、航班号、列车号、身份证号等。

  1. 定义值对象具有的方法

值对象构造时要解析输入参数并进行分解,产生属性值,自然要有获取这些属性的方法,但除此之外还需要有一些业务相关的运算方法,典型的如日期加减。

但要注意值对象不可变,运算方法必须返回新的值对象。

  1. 设计值对象的异常语义

这点非常重要,这也是值对象能应用成功的关键因素。

对象类是否允许错误输入存在,到底是构造时抛出异常,还是将错误延迟到主动校验时。

理想的值对象模型是不允许异常值存在的。它们会在构造时就抛出异常。如:new LocalDate('2000-12-32')或者new LocalDate('null')都会抛出异常。

试想,如果现有数据已经存在异常,那使用值对象去容纳这些数据就会直接报错(反序列化时)。

这样就迫使我们去清洗数据,或者设计一个系统外的数据一致性检查程序,提前发现异常数据并进行修正。

另外一种设计是弱值对象,允许异常值存在,将检验延迟到使用时。这种设计的好处是可以兼容现有数据,在主动检查时才报错,也会节约一定的系统资源。

值对象设计的挑战

  1. 性能开销。值对象潜在的用途是代替原本的字符串处理,但在仅需要透传的场景中,值对象处理会带来额外开销。一种妥协的方法是延迟解析。

  2. 不可变性带来的编程习惯改变。

  3. 与关系模型设计的匹配。关系模型中要求每个字段都是一个不可分割的原子值,并且可以独自更新;而值对象往往是一个复合类型,包含多个属性,并且需要整体更新。

  4. 值对象是否允许附加属性。值对象是封闭的,不能附加可变属性,但在有些编程场合,会特别需要一两个可变属性,以方便存储和传递临时数据,而不需要持久化。这种情况下有可能需要妥协。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容