如何理解DDD中的值对象
引言
实体和值对象是领域驱动设计中的两个重要概念。相对实体而言,值对象更加抽象,理解起来也更晦涩一些。那么该如何理解值对象?我们先来看一下《实现领域驱动设计》书中对值对象的定义:
值对象 (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中的值对象的更多相关文章
- DDD中的值对象如何用NHibernate进行映射
原文:DDD中的值对象如何用NHibernate进行映射 <component/>是NHibernate中一个有趣的特性,即是用来映射DDD(Data-Display-Debuger)概念 ...
- MongoDB学习笔记~MongoDB实体中的值对象
回到目录 注意,这里说的值对象是指在MongoDB实体类中的,并不是DDD中的值对象,不过,两者也是联系,就是它是对类的补充,自己本身没有存在的价值,而在值对象中,也是不需要有主键Id的,这与DDD也 ...
- C++ : 从栈和堆来理解C#中的值类型和引用类型
C++中并没有值类型和引用类型之说,标准变量或者自定义对象的存取默认是没有区别的.但如果深入地来看,就要了解C++中,管理数据的两大内存区域:栈和堆. 栈(stack)是类似于一个先进后出的抽屉.它的 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (44) ------ 第八章 POCO之POCO中使用值对象和对象变更通知
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 8-4 POCO中使用值对象(Complex Type--也叫复合类型)属性 问题 ...
- DDD实战12 值对象不创建表,而是直接作为实体中的字段
这里的值对象如下风格: namespace Order.Domain.PocoModels { //订单地址 //虽然是值对象 但是不继承ValueObject //因为继承ValueObject会有 ...
- 理解--->Java中的值传递&引用传递
转自:http://url.cn/5tL9F5D 值传递和引用传递 值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际 ...
- 如何形象简单地理解java中只有值传递,而没有引用传递?
首先,java中只有值传递,没有引用传递.可以说是"传递的引用(地址)",而不能说是"按引用传递". 按值传递意味着当将一个参数传递给一个函数时,函数接收的是原 ...
- 理解JavaScript中的window对象
前言 每个JavaScript环境都有一个全局对象(global object).在全局范围内创建的任何变量实际上都是这个对象的属性,而任何函数都是它的方法.在浏览器环境中,全局对象是window对象 ...
- c++中关于值对象与其指针以及const值对象与其指针的问题详细介绍
话不多说,先附上一段代码与运行截图 //1 const int a = 10; //const 值对象 int *ap = (int *)&a;//将const int*指针强制转化为int* ...
- 一句话简单理解javascript中的原型对象
通过构造函数F创建的对象实例p 这个对象p的原型对象是 构造函数中prototype属性指向的对象s,这个对象p中也有个非标准的__proto__属性指向构造函数prototype属性所指向的对象s, ...
随机推荐
- 烂怂if-else代码优化方案
0.问题概述 代码可读性是衡量代码质量的重要标准,可读性也是可维护性.可扩展性的保证,因为代码是连接程序员和机器的中间桥梁,要对双边友好.Quora 上有一个帖子: "What are so ...
- Java动态数组及数组排序的三种常用方法
一.动态数组 1.数组的定义: 用于存储相同数据类型的一组连续的存储空间 2.数组的特点: 数组的长度一旦定义,则不可改变 访问数组的元素需要通过下标(索引)访问,下标从0开始 数组是 ...
- C++面试八股文:static和const的关键字有哪些用法?
某日二师兄参加XXX科技公司的C++工程师开发岗位第7面: 面试官:C++中,static和const的关键字有哪些用法? 二师兄:satic关键字主要用在以下三个方面:1.用在全局作用域,修饰的变量 ...
- k8s~RKE的方式升级Rancher集群
kubectl安装 在主机或者远程访问的笔记本上安装kubectl命令行工具 rancher-cluster.yml(RKE配置文件) 通过RKE创建kubernetes集群,需要预先设置ranche ...
- AB实验:科学归因与增长的利器
第一章 AB实验的基本原理和应用 AB实验的相关概念: 3个基本参数:实验参与单元.实验控制参数.实验指标 2个核心价值:验证因果关系.量化策略效果 2个关键特性:先验性.并行性 基本流程:分流 -& ...
- Mybatis-plus自定义Sql注入器
最近在学习mybatis-plus,知道了在mp中通过AbstractSqlInjector将BaseMapper中的方法注入到了Mybatis容器,这样这些方法才可以正常执行. 下面是一个关系图 那 ...
- 统信UOS系统开发笔记(七):在统信UOS系统上使用linuxdeployqt发布qt程序
前言 在ubuntu上发布qt程序相对还好,使用脚本,但是在统信UOS麒麟上发布的时候,因为银河麒麟等不同版本,使用脚本就不太兼容,同时为了实现直接点击应用可以启动应用的效果,使用linuxdep ...
- Apache Hudi 元数据字段揭秘
介绍 Apache Hudi 最初由Uber于 2016 年开发,旨在实现一个交易型数据湖,该数据湖可以快速可靠地支持更新,以支持公司拼车平台的大规模增长. Apache Hudi 现在被业内许多人广 ...
- 【Python】爬虫-Xpath
Xpath 文章参考:https://www.cnblogs.com/mxjhaima/p/13775844.html#案例 安装 pip install lxml 引用 from lxml impo ...
- Codeforces Round #881 (Div. 3) A-F
比赛链接 A 代码 #include <bits/stdc++.h> using namespace std; using ll = long long; int a[57]; bool ...