DDD中的值对象和实体

本文介绍了DDD中的实体和值对象,强调它们在业务建模中的角色。实体具有唯一标识,如用户的身份证号,而值对象如收货地址依赖实体存在,不具唯一标识。值对象通常不独立存在,且设计时多使用Builder模式以确保不变性。在数据库设计中,两者可能单独成表或嵌套于同一表中。总结来说,区分二者的关键在于是否有唯一标识,值对象更适合描述和修饰实体。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

在DDD中有两个比较重要的对象,即值对象和实体。而聚合根就是由这两个对象组成的,所以业务建模前我们都会先定义好实体和值对象,然后再构建聚合根,所以再研究复杂的聚合根之前我们先来研究相对简单但是基础的值对象和实体吧。

定义

  • 实体:实体以 DO(领域对象)的形式存在,拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致(唯一标识)。实体类通常采用充血模型,与这个实体相关的所有业务逻辑都在实体类的方法中实现。简单来说就是有唯一标识+业务行为方法。
  • 值对象:与实体相对应的就是值对象,如果没有唯一标识就是值对象。值对象一般是嵌套在实体里面的。嵌套方式可能有两种,一种是直接嵌套整个Java对象,二是嵌套一个json对象。其次值对象一般是不会改变的,变化的时候也是整体替换。值对象没有太多的业务行为更多的是对实体的一个修饰和描述,离开了实体就没有任何存在的业务意义

举例说明

拿常见的电商项目来说,每个用户一般会有一个唯一标识比如身份证。或者唯一id,而用户会有收货地址,收货地址是没有唯一标识的,是依赖实体而存在的,没有用户地址也就失去了存在的意义。所以收货地址是值对象。

两者对应的关系大致是如下
在这里插入图片描述
如果在数据库中设计,可能是拆装两张表或者使用一张,还有一种对应关系就是使用json去嵌套。这种好处就是不用多维护一张表,查询使用都很方便,唯一的缺点就是修改太麻烦了,毕竟是json
在这里插入图片描述

在我们构建值对象的时候,一般都是会使用Builder模式去构建值对象,然后不暴露set方法,原因如下:
在传统通过new 构造对象,然后各种set方法,在我们出现bug或者一些业务排查的时候,整个对象在各种地方set了属性,我们无法准确定位到对象的某个属性在哪个时候悄悄被改变了,排查起来特别困难,而值对象一般是不可变类似一种配置的概念,正好特别适合这种方式去构造。
但是这种方式也会有一定缺陷,比如某些三方框架都会调用无参构造方法去初始化,然而使用了Builder模式私有化了构造方法,就会有意想不到的报错,但这种错误一般在我们的可控范围,所以具体是否使用这种方式去构造实体也是自己在落地DDD去考虑,但是不用强制去执行,建议灵活应用。毕竟DDD在所有人的理解中都不太一样,团队中使用起来觉得好用合适才是最好的。

总结

实体和值对象有时候很容易区分,有时候又不太容易区分,不过主要还是看有没有唯一标识,如果有唯一表示就是实体,如果能够独立存在也是实体,否则则是值对象,比如像上面的地址,如果脱离了用户其实就没有实际意义了。在不同的业务场景下,值对象和实体可能会互换身份。
上面的实体和值对象是我自己的理解,如果觉得有什么偏差欢迎大家补充。期望在DDD的设计上与大家共同进步。

关于我

觉得文章不错请扫码关注我吧

weichat

### DDD 值对象的概念、定义、使用场景及示例 #### 1. 值对象的定义 值对象(Value Object)是领域驱动设计DDD中的一种核心概念,用于表示那些没有唯一标识的对象。与实体不同,值对象的关注点在于其属性值,而不是身份。如果两个值对象的属性值相同,则它们被认为是相等的[^2]。值对象通常是不可变的,一旦创建就不能更改其状态。 #### 2. 值对象的特性 - **不可变性**:一旦创建,值对象的属性不能被修改。这种特性有助于确保数据的一致性线程安全性。 - **相等性基于值**:两个值对象是否相等取决于它们的属性值是否完全一致,而不是基于内存地址或唯一标识。 - **无唯一标识**:值对象不需要像实体那样具有唯一的标识符。 ```python class Money: def __init__(self, amount: float, currency: str): if amount < 0: raise ValueError("Amount cannot be negative.") self.amount = amount self.currency = currency def __eq__(self, other): if isinstance(other, Money): return self.amount == other.amount and self.currency == other.currency return False def __hash__(self): return hash((self.amount, self.currency)) ``` #### 3. 使用场景 值对象通常用于表示那些在业务逻辑中频繁出现且具有固定意义的数据结构。以下是一些常见的使用场景: - **货币金额**:例如,`Money` 类可以用来表示金额货币类型[^3]。 - **地理位置**:如 `Address` 或 `Coordinate`,这些对象通常由多个属性组成,但不具有唯一标识。 - **时间段**:如 `DateRange` 或 `TimePeriod`,表示一段时间范围。 - **产品规格**:如 `ProductSpecification`,描述产品的尺寸、颜色等属性。 #### 4. 示例代码 以下是一个关于地址的值对象示例: ```python class Address: def __init__(self, street: str, city: str, postal_code: str): self.street = street self.city = city self.postal_code = postal_code def __eq__(self, other): if isinstance(other, Address): return (self.street == other.street and self.city == other.city and self.postal_code == other.postal_code) return False def __hash__(self): return hash((self.street, self.city, self.postal_code)) # 使用示例 address1 = Address("123 Main St", "Springfield", "12345") address2 = Address("123 Main St", "Springfield", "12345") print(address1 == address2) # 输出 True,因为两个地址的值相同 ``` #### 5. 值对象实体的区别 - **身份 vs. 值**:实体通过唯一标识符区分不同的实例,而值对象通过其属性值来判断相等性。 - **可变性**:实体通常是可变的,其状态可以在生命周期内发生变化;值对象则是不可变的,一旦创建就不能修改。 #### 6. 值对象的不可变性优势 不可变性使得值对象在并发环境中更加安全,同时也简化了缓存共享逻辑。由于值对象的值不会改变,因此可以安全地在多个上下文中复用[^2]。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值