引言

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

值对象 (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循环 1.while while( 表达式 ) { //循环内容 } 2.do while do { //循环内容 }while(表达式); 3.for for(初始化; 表达式; 更新) { ...

  2. k8s实战案例之部署Nginx+Tomcat+NFS实现动静分离

    1.基于镜像分层构建及自定义镜像运行Nginx及Java服务并基于NFS实现动静分离 1.1.业务镜像设计规划 根据业务的不同,我们可以导入官方基础镜像,在官方基础镜像的基础上自定义需要用的工具和环境 ...

  3. 绘图;OSPF 虚连接

    绘图;OSPF 虚连接 原图如下 绘图 实验拓扑 实验需求 按照图示分区域配置OSPF 配置虚连接认证 实验步骤 配置相应接口IP地址及loopback 环回口地址 按照图示分区域配置OSPF AR1 ...

  4. WPF 入门笔记 - 03 - 样式基础

    程序的本质 - 数据结构 + 算法 本篇为学习李应保老师所著的<WPF专业编程指南>并搭配WPF开发圣经<WPF编程宝典第4版>以及痕迹大佬<WPF入门基础教程系列> ...

  5. 3. HelloWorld的实现

    恐惧是本能,行动是信仰(在此感谢尚硅谷宋红康老师的教程) 1. 新建 Project - Class 选择"New Project": 指名工程名.使用的 JDK 版本等信息.如下 ...

  6. 【WALT】WALT入口 update_task_ravg() 代码详解

    目录 [WALT]WALT入口 update_task_ravg() 代码详解 代码展示 代码逻辑 ⑴ 判断是否进入 WALT 算法 ⑵ 获取 WALT 算法中上一个窗口的开始时间 ⑶ 如果任务刚初始 ...

  7. EtherCAT 转CCLINK网关连接三菱plc应用案例

    EtherCAT 现场总线协议是由德国倍福公司在 2003 年提出的,该通讯协议拓扑结构十分灵活,数据传输速度快,同步特性好,可以形成各种网络拓扑结构. 捷米特JM-ECT-CCLK  是自主研发的一 ...

  8. Hexo博客yilia主题首页添加helper-live2d模型插件

    插件效果 插件的github地址 插件作者提供了较为详细的安装步骤,我结合自己操作和图示,提供大家. 效果展示:红框内为2d模型,可以随鼠标移动而变化 安装模块: hexo博客根目录选择cmd命令窗口 ...

  9. 快速打开指定目录的cmd

    待解决问题 在使用dirsearch.sqlmap等工具时需要进入对应的目录再打开cmd,现要实现配合utools快速打开对应目录的cmd 解决办法:创捷快捷方式 在对应的文件夹下创建一个快捷方式,对 ...

  10. Oracle分组取最大值

    需求 该SQL是一个子SQL,需要查询出某个人所有过往履历中职务最高的 SELECT a."EMP_ID",a."CADRE_LEVEL" FROM (SELE ...