如何理解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, ...
随机推荐
- 一个.Net强大的Excel控件,支持WinForm、WPF、Android【强烈推荐】
推荐一个强大的电子表单控件,使用简单且功能强大. 项目简介 这是一个开源的表格控制组件,支持Winform.WPF和Android平台,可以方便的加载.修改和导出Excel文件,支持数据格式.大纲.公 ...
- .NET周报 【6月第1期 2023-06-04】
专题 - NanoFramework项目案例 如果有时间,我会在周报中加入一些专题和项目案例的分享,本周就是讨论.NET NanoFramework项目案例的专题,在讨论 NanoFramework ...
- Mybatis的parameterType造成线程阻塞问题分析
一.前言 最近在新发布某个项目上线时,每次重启都会收到机器的 CPU 使用率告警,查看对应监控,持续时长达 5 分钟,对于服务重启有很大风险.而该项目有非常多 Consumer 消费,服务启动后会有大 ...
- Go应用性能优化的8个最佳实践,快速提升资源利用效率!
作者|Ifedayo Adesiyan 翻译|Seal软件 链接|https://earthly.dev/blog/optimize-golang-for-kubernetes/ 优化服务器负载对于确 ...
- Dev 使用RibbonForm打开多标签窗体,主窗体的Text显示一个
最近在开发Dev的项目,一般我们主窗体上边只需要显示应用程序的名称就行了,不需要显示打开Tab页签的名称,百度了很久不知道怎么解决,官方文档只说,RibbonForm的标题是一个组合文本,由Ribbo ...
- 2023-06-22:一所学校里有一些班级,每个班级里有一些学生,现在每个班都会进行一场期末考试 给你一个二维数组 classes ,其中 classes[i] = [passi, totali] 表
2023-06-22:一所学校里有一些班级,每个班级里有一些学生,现在每个班都会进行一场期末考试 给你一个二维数组 classes ,其中 classes[i] = [passi, totali] 表 ...
- 前端vue地图定位并测算当前定位离目标位置距离可用于签到打卡
前端vue地图定位并测算当前定位离目标位置距离可用于签到打卡, 下载完整代码请访问uni-app插件市场地址: https://ext.dcloud.net.cn/plugin?id=12974 效果 ...
- UE5打包SDK未正确安装的问题
正文 Windows(笔者之前用的电脑是windows10,最新电脑使用的是windows11)下UE5打包项目的需要安装Visual Studio. 而且安装的时候需要选择上C++ 游戏开发相关模块 ...
- Blazor前后端框架Known功能介绍:系统安装激活及自定义
本章介绍系统安装与激活及其自定义功能. 概述 框架内置简单的系统安装功能. 录入企业编码.名称.系统名称.产品密钥.管理员密码信息完成安装. 可自定义高级安装功能,如安装数据库等您产品所需的安装信息. ...
- LeetCode 周赛(2023/07/08)渐入佳境
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 和 [BaguTree Pro] 知识星球提问. 往期回顾:LeetCode 单周赛第 351 场 · 一场关于子数 ...