DDD之值对象(Value Object)

领域驱动设计系列文章,点击上方合集↑

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)

关注微信公众号:“小虎哥的技术博客”,让我们一起成为更优秀的程序员❤️!

更多内容点击以下合集:

深入编程原理系列合集

Java 基础系列合集

Java23种设计模式合集

Spring Boot 系列合集

Spring Cloud 微服务系列合集

领域驱动设计系列合集

原文始发于微信公众号(小虎哥的技术博客):DDD之值对象(Value Object)

© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容