领域驱动设计系列文章,点击上方合集↑
1. 开头
值对象(Value Object)是领域驱动设计(DDD)中的一种概念,它是一个轻量级的对象,只包含属性和方法,用于表示领域中的特定值。与实体(Entity)不同,值对象没有唯一的标识符,其相等性是通过值本身决定的而不是标识符。
在《实现领域驱动设计》一书中,作者Vaughn Vernon提到,值对象是相对静态的,不会随时间变化而变化,也不会对应不同的业务行为。因此,它们通常不需要持久化到数据库中,而是嵌入在实体或聚合中,作为实体的属性或聚合的组成部分。
2. 值对象的概念
在领域驱动设计中,值对象是一种没有唯一标识符的对象,其相等性是通过值本身决定的。它通常由简单的属性组成,它们描述了领域中的某些特定概念,比如日期、时间、金额、地址等。值对象不具有生命周期,也不需要被持久化到数据库中。
值对象的特点包括:
-
可以被共享:因为值对象是由其属性定义的,如果两个值对象的属性相同,则它们是相等的。因此,值对象可以作为实体和聚合之间的桥梁,用于共享数据。 -
不可变性:值对象是不可变的,其状态不能被修改。一旦创建后,它的状态就不会再改变。这种不可变性有助于防止意外或非预期的状态变化,并简化了代码的测试和维护。 -
相等性:值对象的相等性是通过它的属性值进行比较的。如果两个值对象的属性相同,则它们是相等的。
3. 应用场景
值对象在DDD中有很多应用场景,以下是其中的几个:
3.1 地址值对象
在许多业务域中,地址是一个重要的值对象。地址值对象通常包含多个属性,如国家、省份、城市、邮政编码等等。地址值对象通常是不可变的,因为地址信息不容易改变。
3.2 金额值对象
金额是在财务领域非常普遍的一个值对象。金额值对象通常由货币和数值部分组成。通常情况下,我们需要实现一些逻辑来保证金额值对象的正确性,比如不能为负数。
3.3 日期时间值对象
日期时间是另一个常用的值对象。日期时间值对象通常包含多个属性,如年、月、日、小时和分钟等等。我们需要实现一些逻辑来确保日期时间值对象的正确性,比如每个月的天数应该在1到31之间。
3.4 共享值对象
值对象可以在聚合和实体之间共享。比如,员工实体可以有一个值对象作为联系信息,这个值对象可以被其他实体或聚合共享。这种共享值对象的方法可以减少重复,在整个应用程序中提高重用性。
4. 值对象的具体例子
假设我们正在开发一个电商网站,其中有一个订单结构。我们知道一个订单包括订单编号,订单日期,订单中的商品列表等属性。另外,我们还需要考虑订单的邮寄地址和账单地址。这时候就可以考虑使用值对象来表示这两个地址。
4.1 地址值对象示例
首先,我们定义一个Address类,表示地址。
public class Address {
private String street;
private String city;
private String state;
private String zipCode;
public Address(String street, String city, String state, String zipCode) {
this.street = street;
this.city = city;
this.state = state;
this.zipCode = zipCode;
}
// getter 和 setter 方法
// 重写 equals 和 hashCode 方法
}
需要注意的是,我们需要重写equals和hashCode方法,以便用于值对象之间的比较。可以通过IDE自动生成方法。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Address address = (Address) o;
return Objects.equals(street, address.street) &&
Objects.equals(city, address.city) &&
Objects.equals(state, address.state) &&
Objects.equals(zipCode, address.zipCode);
}
@Override
public int hashCode() {
return Objects.hash(street, city, state, zipCode);
}
4.2 订单示例
现在我们可以在订单类中使用Address类,来表示邮寄地址和账单地址。
public class Order {
private String orderNumber;
private Date orderDate;
private List<Product> products;
private Address shippingAddress;
private Address billingAddress;
public Order(String orderNumber, Date orderDate, List<Product> products,
Address shippingAddress, Address billingAddress) {
this.orderNumber = orderNumber;
this.orderDate = orderDate;
this.products = products;
this.shippingAddress = shippingAddress;
this.billingAddress = billingAddress;
}
// getter 和 setter 方法
}
可以看到,在Order类中,我们使用Address类来表示邮寄地址和账单地址。这些地址可以作为Order类的属性使用,而我们并不需要关心这些属性中的值对象是如何实现的。
5. 结论
值对象是领域驱动设计中的一个核心概念。它们通常由不可变的属性组成,并且可以被用于描述领域中的各种数据类型。值对象通常被嵌入在实体或聚合中,从而可以被共享和重用。当正确地使用时,值对象可以提高应用程序的可维护性和可重用性。

关注微信公众号:“小虎哥的技术博客”,让我们一起成为更优秀的程序员❤️!
更多内容点击以下合集:
原文始发于微信公众号(小虎哥的技术博客):DDD之值对象(Value Object)
暂无评论内容