引言

实体和值对象是领域驱动设计中的两个重要概念。相对实体而言,值对象更加抽象,理解起来也更晦涩一些。那么该如何理解值对象?我们先来看一下《实现领域驱动设计》书中对值对象的定义:

值对象 (Value Object) 是通过对象属性值来识别的对象,它将多个相关属性组合成一个概念整体,用于描述领域的某个特定方面,并且是一个没有标识符的对象。

值对象是不可变的

值对象本身也是对象,它所表达的是一个值。 那什么是值?比如:小明身高是1.45米,小红买了一支5元的笔,小王期末考试得了98分。这里的1.45米描述的是高度,5元描述的是金额、98分描述的是成绩, 它们代表的都是一个值。值是一个死的东西,是没有生命的。可能你会问:人的身高不是会变化吗? 为什么说它是死的呢?其实这里有一个理解偏差,我可以这样来回答:你去体检测量身高是1.45米,那么体检表上写的就是一个冷冰冰的数字 — 1.45。如果医生不小心写错了,重新修改了这个数字,那它依旧还是一个死的东西,只不过又重新生成了一个新的值而已,这就是值的不可变性。

值对象是基于上下文的

也就是说,同样的东西在不同的业务场景中,它可以看作是实体也可以是值对象。比如:我们去超市购物,你有一张100元,我也有一张100元,我的和你的有区别吗?只要它们的面额是相同的,并且都是人民币,那么它们就是一样的,此时100元就是一个值对象。但是我们知道每张人民币都是有编号的,中国人民银行可以通过编号获取这张货币的各种信息(比如:防伪标志、发行日期、流通状态等),此时100元就是一个实体,因为它需要通过ID来识别。如果有一天该货币被禁止流通了,那么它的生命周期也就结束了。

如何定义值对象

现实中我们很容易将值对象和面向对象语言中的值类型联系起来,因为值类型天然就具备不可变性,但值对象并不完全只能用值类型来表示,也可以是引用类型。比如:一个人的姓名可以用string来表示,尽管string是一个引用类型,但具有不可变性,它就是一个单一属性的值对象。定义如下:

public string Name { get;  private set; }  // 姓名

再如:前面讲到的超市购物100元的例子,我们可以声明一个简单的 Money 类,用来表示一个具有多个属性的值对象。定义如下:

public class Money : ValueObject
{
/// <summary>
/// 面额
/// </summary>
public uint Amount { get; private set; } /// <summary>
/// 币种
/// </summary>
public Currency Currency { get; private set; } public Money(uint amount, Currency currency)
{
Amount = amount;
Currency = currency;
} protected override IEnumerable<object> GetEqualityComponents()
{
yield return Amount;
yield return Currency;
}
}

该值对象包含了 Amount 和 Currency 两个只读属性,表示不可变。其中,Currency是一个枚举类型属性,定义如下:

public enum Currency
{
RMB = 0, // 人民币
USD = 1, // 美元
EUR = 2, // 欧元
}

为了实现值对象的比较,Money 类继承了 ValueObject 这个抽象类,它重写了Equals()方法,  还有相等和不等运算符(== 和 !=)。当两个相同类型的值对象,具有完全一样的属性值,那么它们就是相等的。

var money1 = new Money(100, Currency.RMB);
var money2 = new Money(100, Currency.RMB);
Console.WriteLine(money1 == money2); // true
Console.WriteLine(money1.Equals(money2)); // true

ValueObject 的具体实现可以参考:Implementing value objects - .NET | Microsoft Learn

最后总结

值对象的本质是一个属性集合,这些属性用于描述目的、具有整体概念且不可修改。它可以和其它值对象进行相等性比较,不过不是基于ID,而是基于值对象的属性。因为不可变的特性,它不会对协作对象带来副作用。在领域建模的过程中,值对象可以保证属性归类的清晰和概念的完整性,避免出现零碎的属性。

参考资料

DDD理论学习系列(7)-- 值对象 - 简书 (jianshu.com)

如何运用领域驱动设计 - 值对象 - 句幽 - 博客园 (cnblogs.com)

如何理解DDD中的值对象的更多相关文章

  1. DDD中的值对象如何用NHibernate进行映射

    原文:DDD中的值对象如何用NHibernate进行映射 <component/>是NHibernate中一个有趣的特性,即是用来映射DDD(Data-Display-Debuger)概念 ...

  2. MongoDB学习笔记~MongoDB实体中的值对象

    回到目录 注意,这里说的值对象是指在MongoDB实体类中的,并不是DDD中的值对象,不过,两者也是联系,就是它是对类的补充,自己本身没有存在的价值,而在值对象中,也是不需要有主键Id的,这与DDD也 ...

  3. C++ : 从栈和堆来理解C#中的值类型和引用类型

    C++中并没有值类型和引用类型之说,标准变量或者自定义对象的存取默认是没有区别的.但如果深入地来看,就要了解C++中,管理数据的两大内存区域:栈和堆. 栈(stack)是类似于一个先进后出的抽屉.它的 ...

  4. 《Entity Framework 6 Recipes》中文翻译系列 (44) ------ 第八章 POCO之POCO中使用值对象和对象变更通知

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 8-4  POCO中使用值对象(Complex Type--也叫复合类型)属性 问题 ...

  5. DDD实战12 值对象不创建表,而是直接作为实体中的字段

    这里的值对象如下风格: namespace Order.Domain.PocoModels { //订单地址 //虽然是值对象 但是不继承ValueObject //因为继承ValueObject会有 ...

  6. 理解--->Java中的值传递&引用传递

    转自:http://url.cn/5tL9F5D 值传递和引用传递 值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际 ...

  7. 如何形象简单地理解java中只有值传递,而没有引用传递?

    首先,java中只有值传递,没有引用传递.可以说是"传递的引用(地址)",而不能说是"按引用传递". 按值传递意味着当将一个参数传递给一个函数时,函数接收的是原 ...

  8. 理解JavaScript中的window对象

    前言 每个JavaScript环境都有一个全局对象(global object).在全局范围内创建的任何变量实际上都是这个对象的属性,而任何函数都是它的方法.在浏览器环境中,全局对象是window对象 ...

  9. c++中关于值对象与其指针以及const值对象与其指针的问题详细介绍

    话不多说,先附上一段代码与运行截图 //1 const int a = 10; //const 值对象 int *ap = (int *)&a;//将const int*指针强制转化为int* ...

  10. 一句话简单理解javascript中的原型对象

    通过构造函数F创建的对象实例p 这个对象p的原型对象是 构造函数中prototype属性指向的对象s,这个对象p中也有个非标准的__proto__属性指向构造函数prototype属性所指向的对象s, ...

随机推荐

  1. Java如何生成随机数?要不要了解一下!

    前言 我们在学习 Java 基础时就知道可以生成随机数,可以为我们枯燥的学习增加那么一丢丢的乐趣.本文就来介绍 Java 随机数. 一.Random类介绍 在 Java 中使用 Random 工具类来 ...

  2. 20200630 excel365 选中一个单元格,对应的行和列都高亮

    Excel默认只高亮选中单元格的行标和列标,在整理数据时容易眼花,如能把这一行和列都高亮岂不是更好.方法在此: 1 打开"开发工具"菜单 默认这一项是隐藏的.文件-选项-自定义功能 ...

  3. 编译器性能调优:使用C++11实现高效编译器

    目录 1. 引言 2. 技术原理及概念 2.1. 基本概念解释 2.2. 技术原理介绍 <编译器性能调优:使用C++11实现高效编译器> 编译器是计算机程序的入口点,将源代码转换为可执行文 ...

  4. PHP处理模板 cookie优先 检测用户登录

    <?php// +----------------------------------------------------------------------// | easy pay [ pa ...

  5. 4.2 针对PE文件的扫描

    通过运用LyScript插件并配合pefile模块,即可实现对特定PE文件的扫描功能,例如载入PE程序到内存,验证PE启用的保护方式,计算PE节区内存特征,文件FOA与内存VA转换等功能的实现,首先简 ...

  6. 从0开发WebGPU渲染引擎:开篇

    大家好,本系列会从0开始,开发一个基于WebGPU的路径追踪渲染器,使用深度学习降噪.DLSS等AI技术实现实时渲染:并且基于自研的低代码开发平台,让用户可以通过可视化拖拽的方式快速搭建自定义的Web ...

  7. java解析CSV文件(zipFiles 打成压缩包 exportObeEventDataExcel 前端页面响应)

    JAR包及代码17:39:09 <!-- https://mvnrepository.com/artifact/com.opencsv/opencsv --> <dependency ...

  8. 详解RISC v中断

    声明 本文为本人原创,未经许可严禁转载.部分图源自网络,如有侵权,联系删除. RISC-V 中断与异常 trap(陷阱)可以分为异常与中断.在 RISC v 下,中断有三种来源:software in ...

  9. 活动回顾:Flutter实时音视频应用场景实践

    11月7日,即构和上海GDG技术社区联合举办了实时音视频技术云上技术分享专场,来自即构科技和Bilibili的资深技术专家进行了深度分享.大会吸引了500+开发人员交流.观看,并在活动过程中与分享嘉宾 ...

  10. 如何在Spring Boot中记录用户系统操作流程?

    在现代Web应用程序中,记录用户系统操作流程对于监控用户行为.进行故障排查.安全审计等方面都是非常重要的.在本篇博客中,我们将介绍如何在Spring Boot中使用AOP(面向切面编程)和日志框架来实 ...