领域驱动设计(DDD)——实体(Entity)和值对象(Value Object) 详解和示例

        在领域驱动设计(DDD,Domain-Driven Design)中,实体(Entity)和值对象(Value Object)是两种核心的领域模型概念,它们用于表示业务中的对象。

        在领域驱动设计(DDD)中,实体(Entity)和值对象(Value Object)是两种核心的领域模型类型,用于表示领域中的业务概念和数据。理解实体与值对象的区别和使用场景是掌握领域建模的关键。

DDD架构
DDD架构

        本文将帮助理解两者的区别以及如何在生产开发中正确区分和定义。


1. 实体(Entity)

定义

        实体是领域中的一个业务概念,它具有唯一标识(ID),这个标识用来区分同类型的不同实体。
        即使实体的其他属性值发生变化,只要它的唯一标识(ID)保持不变,它仍然是同一个实体。

特点
  1. 唯一标识(ID):
    实体必须具有唯一标识(例如主键 ID),通过唯一标识来确认其身份。

  2. 可变性:
    实体的属性值可以随时间变化。例如,用户的地址或订单状态可能会更新。

  3. 生命周期:
    实体具有明确的生命周期,从创建到销毁可能经历多个状态变化。

  4. 业务行为:
    实体通常是业务逻辑的主要载体,因为它们承载了核心的业务操作。

实体的关键特性:
  • 通过唯一标识判断同一性。
  • 是业务中的“独立角色”。
  • 属性的变化不影响其身份。

示例

在电商系统中,一个订单(Order)就是一个实体:

  • 它有唯一的订单 ID 来标识它。
  • 订单的状态(如“已创建”、“已发货”)可能会随时间变化。

示例代码:

public class Order {
    private final String orderId; // 唯一标识
    private String status;        // 订单状态
    private double totalAmount;   // 总金额

    public Order(String orderId, String status, double totalAmount) {
        this.orderId = orderId;
        this.status = status;
        this.totalAmount = totalAmount;
    }

    public String getOrderId() {
        return orderId;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public double getTotalAmount() {
        return totalAmount;
    }

    public void setTotalAmount(double totalAmount) {
        this.totalAmount = totalAmount;
    }

    // 重写 equals 和 hashCode 方法,仅比较 id
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Order order = (Order) o;
        return orderId.equals(order.orderId);
    }

    @Override
    public int hashCode() {
        return orderId.hashCode();
}


2. 值对象(Value Object)

定义

值对象表示业务中的属性或状态,它没有唯一标识,通过属性值相等来判断两个值对象是否相等。

特点
  1. 无唯一标识:
    值对象没有唯一标识,它通过属性值来判断是否相同(两个值对象如果属性值相同,就认为它们是相等的)。

  2. 不可变性:
    值对象通常是不可变的。一旦创建,其属性值不能再被修改。如果需要修改值对象,应创建一个新的实例(创建一个新的值对象来替代)。

  3. 用于描述属性:
    值对象通常用来表示实体的属性或某些业务概念,帮助实体表达更复杂的业务逻辑,如地址(Address)、货币金额(Money)等。

  4. 业务逻辑的载体:
    值对象也可以包含与其相关的业务逻辑。例如,货币金额对象可以包含加减操作。

值对象的关键特性:
  • 通过属性值判断相等性。
  • 主要用来描述领域中的“值”。
  • 是不可变的(不可变性可以提高代码的安全性和可维护性)。

示例

在电商系统中,一个地址(Address)就是一个值对象:

  • 它没有唯一标识,只有地址的属性值(如街道、城市)来判断是否相等。
  • 如果地址发生变化,则需要创建一个新的地址对象。

示例代码:

public class Address {
    private final String street;  // 街道
    private final String city;    // 城市
    private final String zipCode; // 邮政编码

    public Address(String street, String city, String zipCode) {
        this.street = street;
        this.city = city;
        this.zipCode = zipCode;
    }

    public String getStreet() {
        return street;
    }

    public String getCity() {
        return city;
    }

    public String getZipCode() {
        return zipCode;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address = (Address) o;
        return street.equals(address.street) &&
               city.equals(address.city) &&
               zipCode.equals(address.zipCode);
    }

    @Override
    public int hashCode() {
        return Objects.hash(street, city, zipCode);
    }
}


3. 实体与值对象的区别

特性实体(Entity)值对象(Value Object)
标识有唯一标识,用于区分实例无唯一标识,通过属性值比较是否相等
相等性判断基于唯一标识判断相等基于属性值判断相等
可变性通常是可变的通常是不可变的
用途表示业务中的独立角色表示业务中的属性或状态
生命周期生命周期明确,可以存在较长时间生命周期通常由其所属的实体决定,随实体一起存在与销毁
业务场景用户、订单、商品等地址、货币金额、坐标等

4. 实体与值对象的使用场景

实体的使用场景

        用户管理系统: 用户、订单、商品等独立对象。

        订单系统: 每个订单具有唯一标识(订单号),即使订单的状态或金额发生变化,它仍是同一个订单。

  • 订单(Order):具有唯一标识和生命周期,状态可能变化。
  • 用户(User):每个用户都由唯一标识(如用户 ID)标识。
  • 产品(Product):每个产品都有唯一的 SKU 或产品编号。
值对象的使用场景

        地址管理: 地址通常作为用户的属性,如果地址发生变化,我们只需替换成一个新的值对象。

        货币管理: 金额和币种的组合可以作为一个值对象(如 100 USD 和 200 CNY)。

  • 地址(Address):表示街道、城市、邮政编码等,无需唯一标识。
  • 金额(Money):表示货币金额(如 100 元),只需要通过数值和币种比较。
  • 时间范围(DateRange):表示开始时间和结束时间。

5. 为什么区分实体和值对象很重要?

  1. 建模更清晰:

    如果不清楚区分实体和值对象,容易导致模型复杂化,例如为值对象误设唯一标识,或者让实体中充满不必要的状态。
  2. 简化业务逻辑:

    值对象的不可变性可以减少并发修改的问题,同时值对象的属性值比较也更自然。
  3. 提高性能:

    值对象通常可以直接复用,不需要像实体那样额外进行唯一性校验或管理。
  4. 降低代码复杂度:

    通过值对象封装属性,可以避免将所有细节直接暴露在实体中,从而让代码更易于维护。

6. 在生产开发中如何区分和定义实体与值对象

        在实际开发中,我们需要根据业务需求来判断一个对象是实体还是值对象。以下是两者的定义和设计准则。


6.1 如何定义实体

判断准则:

  • 是否需要唯一标识:
    如果业务中需要明确区分某个对象的“身份”,那么它是实体。例如,一个用户的 ID 是其身份的唯一标识,即使姓名或其他信息发生变化,用户的身份不变。

  • 是否有独立生命周期:
    如果对象的生命周期是独立于其他对象的,它通常是实体。例如,订单可以单独存在,即使用户被删除,订单仍然存在。


6.2  如何定义值对象

判断准则:

  • 是否依赖于属性值:
    如果对象没有唯一标识,并且其意义完全由属性值决定,那么它是值对象。例如,货币金额(100 USD)是值对象,不需要唯一标识。

  • 是否是不可变的:
    值对象应当设计为不可变。一旦创建了值对象,其属性值不能再被修改。


6.3 在生产开发中的设计注意事项

  1. 明确唯一标识的需求:

    如果对象需要唯一标识,则设计为实体;如果对象仅根据属性值比较相等,则设计为值对象。
  2. 保持值对象的不可变性:

    在设计值对象时,避免提供修改属性值的方法(如 Setter),确保它是不可变的。
  3. 避免误用:

    不要为了方便,将所有对象都设计成实体或值对象;区分的基础是业务需求。
  4. 性能优化:

    如果值对象过于复杂(如嵌套属性过多),应注意其在内存中的创建和比较成本。

6.4 小结

  • 实体: 代表有唯一标识的独立业务对象,用来表示业务中的“谁”。
  • 值对象: 用来表示业务中的属性或状态,没有唯一标识,用来表示“什么”。
  • 在生产开发中,根据业务需求区分实体与值对象,并严格遵守不可变性(值对象)和唯一标识(实体)的设计原则,从而提升代码的可维护性和业务的表达能力。

7. 实体与值对象的组合使用

在实际场景中,实体和值对象往往是组合使用的。例如:

  • 一个订单(实体 Order)可能包含多个商品明细,每个商品明细是一个值对象。

示例代码:

import java.util.ArrayList;
import java.util.List;

public class Order {
    private final String orderId; // 订单唯一标识
    private final String userId;  // 用户唯一标识
    private final List<OrderItem> items; // 订单商品明细

    public Order(String orderId, String userId) {
        this.orderId = orderId;
        this.userId = userId;
        this.items = new ArrayList<>();
    }

    public String getOrderId() {
        return orderId;
    }

    public String getUserId() {
        return userId;
    }

    public List<OrderItem> getItems() {
        return items;
    }

    public void addItem(OrderItem item) {
        this.items.add(item);
    }
}

/**
 * 订单明细(值对象)。
 * 表示每个订单中商品的具体信息。
 */
public class OrderItem {
    private final String productId; // 商品 ID
    private final int quantity;     // 数量
    private final double price;     // 单价

    public OrderItem(String productId, int quantity, double price) {
        this.productId = productId;
        this.quantity = quantity;
        this.price = price;
    }

    public String getProductId() {
        return productId;
    }

    public int getQuantity() {
        return quantity;
    }

    public double getPrice() {
        return price;
    }
}


总结

  • 实体(Entity)表示领域中的核心业务角色,具有唯一标识,属性和状态可变,生命周期明确。
  • 值对象(Value Object)表示领域中的属性或状态,通常不可变,通过属性值判断相等。

        理解实体与值对象的区别和正确使用场景,可以帮助我们设计更清晰的领域模型,减少复杂度,增强代码的可维护性和可读性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值