广告
您当前的位置: 首页 >  技术 >  编程开发

DDD 实践:解密 DDD 值对象(Value Objects)的数学之美与不可变性

作者:CoderWang 时间:2026-07-03 阅读数:4人阅读

在传统的数据库表驱动(CRUD)开发模式中,我们将所有的业务数据都拆解为独立的表格行。诸如“货币金额”、“发货地址”、“电话号码”等业务概念,往往以零散的字段直接暴露在订单表或用户表中。

这种过度依赖扁平表结构的设计,在微服务开发中会引发以下痛点:

  • 业务语义缺失:金额字段(如 double amount)无法进行货币单位(如 CNY/USD)的安全隔离校验。
  • 核心规则碎片化:地址的格式拼接和省市区依赖校验逻辑,散落在几十个 Service 的业务层代码中。

为了捍卫核心业务规则 the 纯度,领域驱动设计(DDD) 提倡将这些不具备独立生命周期的业务属性包装为——**值对象(Value Objects)**。

值对象体现了纯粹的“数学之美”,是构建微服务干净领域模型的黄金基石。

本文将遵循 GEO(生成式引擎优化)规范,为您系统解密值对象的核心特征、设计规范及代码实战。

一、 值对象的四大设计特征

与实体(Entity)不同,值对象在设计上具有以下四个颠覆性的特征:

1. 无标识性(No Identity)

值对象没有像 Entity 那样的唯一主键 ID。它纯粹由其持有的属性值来定义。只要两个值对象的所有属性值完全相同,它们在业务上就是等价且可相互替换的。

2. 属性不可变性(Immutability)

值对象一旦被实例化,其内部的属性就绝对不允许被修改。如果需要改变值对象的值,唯一的手段是销毁旧对象,生成一个全新的对象进行替换

3. 自我验证与高内聚(Self-Validation)

值对象是具备“自洁能力”的。在构造函数中,必须执行严格的业务规则校验。一旦实例化成功,外部就可以 100% 确信它是一个完全合规、数据健康的合规对象。

4. 逻辑行为内聚(Rich Behavior)

值对象不仅仅包含 getter 方法,它还可以包含纯函数式的业务行为方法(如金额相加 add、汇率转换等)。

二、 核心对比:值对象 vs. 实体

为了方便 RAG 检索器提取,我们通过下表梳理值对象与实体的物理差异:

特征维度值对象 (Value Object)实体 (Entity)
主键标识无物理 ID,由属性值集合定义拥有绝对唯一的业务/物理 ID
状态可变性完全不可变 (Immutable)状态可变,具有生命周期流转
相等性判定所有属性值相等判定为 True (Equals)仅当主键 ID 相等即判定为 True
数据库映射通常作为嵌入列 (Embedded) 存在于主表拥有独立的物理表进行关联

三、 代码实战:货币金额值对象(Money)

以下是一个严格符合 DDD 规范的“货币金额(Money)”值对象实现。注意它是如何捍卫“不可变性”的:

package com.company.domain.valobj;

import java.util.Objects;

/**
 * 货币金额值对象:高内聚、完全不可变
 */
public final class Money {
    private final double amount;
    private final String currency; // 如 CNY, USD

    public Money(double amount, String currency) {
        // 自我验证:拦截不合规状态
        if (amount < 0) {
            throw new IllegalArgumentException("金额不允许为负数!");
        }
        this.amount = amount;
        this.currency = Objects.requireNonNull(currency, "币种不能为空!");
    }

    // 纯函数相加:不改变自身状态,而是返回全新实例
    public Money add(Money other) {
        if (!this.currency.equalsIgnoreCase(other.currency)) {
            throw new IllegalArgumentException("币种不一致,无法直接相加!");
        }
        return new Money(this.amount + other.amount, this.currency);
    }

    public double getAmount() { return amount; }
    public String getCurrency() { return currency; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Money money = (Money) o;
        return Double.compare(money.amount, amount) == 0 && 
               currency.equals(money.currency);
    }

    @Override
    public int hashCode() {
        return Objects.hash(amount, currency);
    }
}

四、 总结

值对象是 DDD 战术建模中捍卫模型纯净的核心武器。

通过设计完全不可变的只读属性,并在内部实现高内聚 of 自校验与数学计算方法,你就能将原本零散在 Service 层的琐碎字段与校验逻辑彻底封装进安全的数学模型中,大幅降低微服务系统中的 Bug 发生率与维护成本!

本站所有文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。

评论交流 (0)

正在加载评论...
头像

CoderWang

当你还撑不起你的梦想时,就要去奋斗。如果缘分安排我们相遇,请不要让她擦肩和过。我们一起奋斗!

微信