08、真实世界的映照-DDD值对象

284 阅读5分钟

值对象(value objectobject)是快照,固定不变,对象创建完成之后,就不能修改里面的所有属性。

比如,今天吃饭花了 20 块钱,就可以定义成值对象,因为吃饭花了 20 块钱已经是事实,不会再有变化。

值对象的常见例子包括数字,比如3、10和293.51;或者文本字符串,比如“hello,world”和“Domain-Driven Design”;或者日期、时间;还有更加详细的对象,比如某人的全名,其中包含姓氏、名字和头衔;再比如货币、颜色、电话号码和邮寄地址等。

值对象,也是用来模拟现实世界使用。

现实世界中的物事,在生命周期内有变化的事物用实体来建模,在生命周期内无变化的事物用值对象来建模。

比如,常用的审计日志、用户操作日志,这些就很适合使用值对象来建模。

比如,用户每一笔消费记录,也很适合用值对象来建模。

比如,保证书、保密协议、签名,也很适合用值对象来建模。

image.png

值对象与实体的区别

值对象与实体的区别是,值对象在生命周期内属性不能变,实体在生命周期内属性可以变。

值对象能有唯一标识吗?

值对象可以有唯一标识,但是唯一标识不是值对象必须拥有的。

为什么说值对象可以有唯一标识呢?既然值对象也是真实物理世界的映射,那么,每一笔消费记录在真实世界中都有一个唯一的流水号,难道这个流水号,不是唯一标识吗?

值对象,能存储到数据库中吗?

值对象能存储到数据库中,存储到数据库中的数据并不只有实体,领域事件也能存储到数据库中。

值对象,能有行为方法吗?

值对象中能有行为方法,但行为方法不能改变值对象内容的属性值。举个例子,身份证,可以建模成值对象,身份证值对象有个行为方法获取身份证号码,但是因为信息敏感的原因,获取身份证号码这个行为方法,返回的是掩码后的身份证号。

值对象,能被修改吗?

因为值对象是真实世界的快照,因此值对象是固定的,不能被修改的,值对象内部的所有属性,也都不能被修改。 在Java中,值对象的Java类中,属性不能有public、protected的set方法,同时最好被final修饰。

import lombok.Getter; 
@Getter 
public class IdCardVO 
{ 
    // 身份证号码
    private final String cardNo; 
    // 名字
    private final String personName; 
    // 民族
    private final String nation;
    // 工址
    private final String address; 
    
    public IdCardVO(String cardNo, String personName, String nation, String address) { 
        // 检查数据是否正确 
        if (null == cardNo || personName == null || nation == null || address == null || cardNo.length() != 18){ 
            throw new IllegalArgumentException("参数不正确");
        } 
        this.cardNo = cardNo; 
        this.personName = personName;
        this.nation = nation; this.address = address;
    }
        
    /** 
    * 返回身份证号码(打上掩码)
    * 
    * @return 身份证号码 
    */ 
    public String cardNo() { return cardNo.substring(0, 13) + "*****"; }
}

什么时候该用值对象?

1、用于方法传参

相信很多写过代码的同学,都经常遇到某个方法的名字叫做getXxx(params),但却是在getXxxx方法里面修改了params的属性值……。因此,当需要排查问题的时候,需要每个方法都点击进去一行行看代码,因为getXxxx方法修改params的属性值这种场景很常见。

理想的状态下,我们是不希望传入的参数被方法内部修改的。这时候,我们就可以使用值对象作为传参,因为值对象具有不可被修改的性质,因此很适合用于方法传参。

2、用于对真实世界中属性不可被改变的对象进行建模

因为值对象的本质是映射真实世界中属性不可被改变的对象,所以很适合用来对这类对象进行建模

3、用于系统集成的传参与返回值

无论系统集成之间是通过消息队列这种方式,还是通过API这种方式,还是其他的方式,都要求参数(请求参数和返回参数)具有不可被修改的性质,因此很适合使用值对象。

值对象中真的不能有set方法吗?

很多json之类框架,都要求Java类中提供get、set方法。很多orm之类的框架,也要求Java类中提供get、set方法。很多人因为这些框架的限制set方法,而不敢使用值对象。

如果你的思想被教条主义所局限了,那么,值对象中不能有任何set方法。

DDD是一个方法论,值对象是其中一个概念。

难道Java实体中有set方法就不能是值对象了吗?我们在编写的时候严格执行,只要是被定义成值对象的Java类,都不能去调用里面的set方法,这难道不是在践行DDD值对象的本质吗?

如果,所用开发语言是Javascript呢?Javascript可没有提供像Java一样的private语法对属性进行封装,难道Javascript就不能定义值对象了吗?我们在编写的时候严格执行,只要是被定义成值对象的Javascript对象,都不能去更改里面的属性值,这难道不是在践行DDD值对象的本质吗?

因此,值对象中能有set方法,但是,在编码的时候,必须要人为限制,不能写代码去调用set方法,set方法只能由orm、gson之类的框架调用!