ValueObject
ValueObject
When programming, I often find it's useful to represent things as a compound混合物. A 2D coordinate consists of an x value and y value. An amount of money consists of a number and a currency. A date range consists of start and end dates, which themselves can be compounds of year, month, and day.
As I do this, I run into the question of whether two compound objects are the same. If I have two point objects that both represent the Cartesian笛卡尔 coordinates of (2,3), it makes sense to treat them as equal. Objects that are equal due to the value of their properties, in this case their x and y coordinates, are called value objects.
But unless I'm careful when programming, I may not get that behavior in my programs
Say I want to represent a point in JavaScript.
const p1 = {x: 2, y: 3};
const p2 = {x: 2, y: 3};
assert(p1 !== p2); // NOT what I want
Sadly that test passes. It does so because JavaScript tests equality for js objects by looking at their references, ignoring the values they contain.
In many situations using references rather than values makes sense. If I'm loading and manipulating a bunch of sales orders, it makes sense to load each order into a single place. If I then need to see if the Alice's latest order is in the next delivery, I can take the memory reference, or identity, of Alice's order and see if that reference is in the list of orders in the delivery. For this test, I don't have to worry about what's in the order. Similarly I might rely on a unique order number, testing to see if Alice's order number is on the delivery list.
Therefore I find it useful to think of two classes of object: value objects and reference objects, depending on how I tell them apart [1]. I need to ensure that I know how I expect each object to handle equality and to program them so they behave according to my expectations. How I do that depends on the programming language I'm working in.
Some languages treat all compound data as values. If I make a simple compound in Clojure, it looks like this.
> (= {:x 2, :y 3} {:x 2, :y 3})
true
That's the functional style - treating everything as immutable values.
But if I'm not in a functional language, I can still often create value objects. In Java for example, the default point class behaves how I'd like.
assertEquals(new Point(2, 3), new Point(2, 3)); // Java
The way this works is that the point class overrides the default equals
method with the tests for the values. [2] [3]
I can do something similar in JavaScript.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
equals (other) {
return this.x === other.x && this.y === other.y;
}
}
const p1 = new Point(2,3);
const p2 = new Point(2,3);
assert(p1.equals(p2));
The problem with JavaScript here is that this equals method I defined is a mystery to any other JavaScript library.
const somePoints = [new Point(2,3)];
const p = new Point(2,3);
assert.isFalse(somePoints.includes(p)); // not what I want //so I have to do this
assert(somePoints.some(i => i.equals(p)));
This isn't an issue in Java because Object.equals
is defined in the core library and all other libraries use it for comparisons (==
is usually used only for primitives).
One of the nice consequences of value objects is that I don't need to care about whether I have a reference to the same object in memory or a different reference with an equal value. However if I'm not careful that happy ignorance can lead to a problem, which I'll illustrate with a bit of Java.
Date retirementDate = new Date(Date.parse("Tue 1 Nov 2016")); // this means we need a retirement party
Date partyDate = retirementDate; // but that date is a Tuesday, let's party on the weekend
partyDate.setDate(5); assertEquals(new Date(Date.parse("Sat 5 Nov 2016")), retirementDate);
// oops, now I have to work three more days :-(
This is an example of an Aliasing Bug, I change a date in one place and it has consequences beyond what I expected [4]. To avoid aliasing bugs I follow a simple but important rule: value objects should be immutable. If I want to change my party date, I create a new object instead.
Date retirementDate = new Date(Date.parse("Tue 1 Nov 2016"));
Date partyDate = retirementDate; // treat date as immutable
partyDate = new Date(Date.parse("Sat 5 Nov 2016")); // and I still retire on Tuesday
assertEquals(new Date(Date.parse("Tue 1 Nov 2016")), retirementDate);
Of course, it makes it much easier to treat value objects as immutable if they really are immutable. With objects I can usually do this by simply not providing any setting methods. So my earlier JavaScript class would look like this: [5]
class Point {
constructor(x, y) {
this._data = {x: x, y: y};
}
get x() {return this._data.x;}
get y() {return this._data.y;}
equals (other) {
return this.x === other.x && this.y === other.y;
}
}
While immutability is my favorite technique to avoid aliasing bugs, it's also possible to avoid them by ensuring assignments always make a copy. Some languages provide this ability, such as structs in C#.
Whether to treat a concept as a reference object or value object depends on your context. In many situations it's worth treating a postal address as a simple structure of text with value equality. But a more sophisticated mapping system might link postal addresses into a sophisticated hierarchic model where references make more sense. As with most modeling problems, different contexts lead to different solutions. [6]
It's often a good idea to replace common primitives, such as strings, with appropriate value objects. While I can represent a telephone number as a string, turning into a telephone number object makes variables and parameters more explicit (with type checking when the language supports it), a natural focus for validation, and avoiding inapplicable behaviors (such as doing arithmetic on integer id numbers).
Small objects, such as points, monies, or ranges, are good examples of value objects. But larger structures can often be programmed as value objects if they don't have any conceptual identity or don't need share references around a program. This is a more natural fit with functional languages that default to immutability. [7]
I find that value objects, particularly small ones, are often overlooked - seen as too trivial to be worth thinking about. But once I've spotted a good set of value objects, I find I can create a rich behavior over them. For taste of this try using a Range class and see how it prevents all sorts of duplicate fiddling with start and end attributes by using richer behaviors. I often run into code bases where domain-specific value objects like this can act as a focus for refactoring, leading to a drastic simplification of a system. Such a simplification often surprises people, until they've seen it a few times - by then it is a good friend.
Acknowledgements
James Shore, Beth Andres-Beck, and Pete Hodgson shared their experiences of using value objects in JavaScript.
Graham Brooks, James Birnie, Jeroen Soeters, Mariano Giuffrida, Matteo Vaccari, Ricardo Cavalcanti, and Steven Lowe provided valuable comments on our internal mailing lists.
Further Reading
Vaughn Vernon's description is probably the best in-depth discussion of value objects from a DDD perspective. He covers how to decide between values and entities, implementation tips, and the techniques for persisting value objects.
The term started gaining traction in the early noughties. Two books that talk about them from that time are PoEAA and DDD. There was also some interesting discussion on Ward's Wiki.
One source of terminological confusion is that around the turn of the century some J2EE literature used "value object" for Data Transfer Object. That usage has mostly disappeared by now, but you might run into it.
Notes
1: In Domain-Driven Design the Evans Classification contrasts value objects with entities. I consider entities to be a common form of reference object, but use the term "entity" only within domain models while the reference/value object dichotomy is useful for all code.
2: Strictly this is done in awt.geom.Point2D, which is a superclass of awt.Point
3: Most object comparisons in Java are done with equals
- which is itself a bit awkward since I have to remember to use that rather than the equals operator ==
. This is annoying, but Java programmers soon get used to it since String behaves the same way. Other OO languages can avoid this - Ruby uses the ==
operator, but allows it to be overridden.
4: There is robust competition for the worst feature of the pre-Java-8 date and time system - but my vote would be this one. Thankfully we can avoid most of this now with Java 8's java.time
package
5: This isn't strictly immutable since a client can manipulate the _data
property. But a suitably disciplined team can make it immutable in practice. If I was concerned that a team wouldn't be disciplined enough I might use freeze
. Indeed I could just use freeze on a simple JavaScript object, but I prefer the explicitness of a class with declared accessors.
6: There is more discussion of this in Evans's DDD book.
7: Immutability is valuable for reference objects too - if a sales order doesn't change during a get request, then making it immutable is valuable; and that would make it safe to copy it, if that were useful. But that wouldn't make the sales order be a value object if I'm determining equality based on a unique order number.
ValueObject的更多相关文章
- ValueObject的理解
思考ValueObject应该更多从内存的角度思考,而非DB持久化的角度. 例如: public class A { public int Id { get; set; } public Addres ...
- 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑
阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...
- 如何一步一步用DDD设计一个电商网站(二)—— 项目架构
阅读目录 前言 六边形架构 终于开始建项目了 DDD中的3个臭皮匠 CQRS(Command Query Responsibility Segregation) 结语 一.前言 上一篇我们讲了DDD的 ...
- ABP文档翻译--值对象
本人是ABP初学者,在看英文文档和@tkb至简 的ABP框架理论研究总结(典藏版)时,发现大神@tkb至简中少了对Value Objects的翻译,看文档是新的,大神没时间把,小弟给补充上. 介绍 值 ...
- ABP框架 - 值对象
文档目录 本节内容: 简介 值对象基类 最佳实践 简介 “一个表示领域的一个描述性方面的没有概念上的身份对象,称为值对象.“(Eric Evans). 与一个有身份(Id)实体相反,一个值对象没有身份 ...
- Redis与KV存储(RocksDB)融合之编码方式
Redis与KV存储(RocksDB)融合之编码方式 简介 Redis 是目前 NoSQL 领域的当红炸子鸡,它象一把瑞士军刀,小巧.锋利.实用,特别适合解决一些使用传统关系数据库难以解决的问题.Re ...
- 为什么要在游戏开发中使用ECS模式
http://www.richardlord.net/blog/why-use-an-entity-framework Why use an entity system framework for g ...
- errored out in DoExecute, couldn't PrepareToExecuteJITExpression
error: Couldn't materialize struct: size of variable <varName> disagrees with the ValueObject' ...
- JsonHelper MergeJsonTemplate
namespace Test { using Newtonsoft.Json; using System; using System.Collections.Generic; using System ...
随机推荐
- SCRIPT438: 对象不支持“indexOf”属性或方法
SCRIPT438: 对象不支持“indexOf”属性或方法 indexOf()的用法:返回字符中indexof(string)中字串string在父串中首次出现的位置,从0开始!没有返回-1:方便判 ...
- npm 使用指南参考
[阮一峰npm scripts基本教程] [rimraf 跨平台删除文件] [ts-loader 安装问题] [nvm 安装使用] [npm镜像的问题] [webpack 如何引入jquery]web ...
- 学号 20175313《Java程序设计》 第七周学习总结
目录 一.教材学习内容总结 二.教材学习中的问题和解决过程 三.代码托管 四.心得体会 五.学习进度条 六.参考资料 一.教材学习内容总结 第八章主要内容 了解String类 常量对象:常量池中的数据 ...
- 关于映射路径@ReuqestMapping的总结
何谓映射路径呢? 映射路径,就是匹配请求路径和执行方法关系的路径 基于注解的映射路径可以忽略前后缀,如: @RequestMapping(value="/say.do") @Req ...
- 【Idea】-NO.163.Idea.2 -【How to show the horizontal scroll?】
Style:Mac Series:Java Since:2018-09-10 End:2018-09-10 Total Hours:1 Degree Of Diffculty:5 Degree Of ...
- zt (stack overflow 介绍)
这是「解密 Stack Overflow 架构」系列的第一篇,本系列会有非常多的内容.欢迎阅读并保持关注. 为了便于理解本文涉及到的东西到底都干些了什么,让我先从 Stack Overflow 每天平 ...
- MySQL建表 TIMESTAMP 类型字段问题
Incorrect table definition; there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT ...
- 浅析MySQL InnoDB的隔离级别
MySQL InnoDB存储引擎中事务的隔离级别有哪些?对应隔离级别的实现机制是什么? 本文就将对上面这两个问题进行解答,分析事务的隔离级别以及相关锁机制. 隔离性简介 隔离性主要是指数据库系统提供一 ...
- #WEB安全基础 : HTTP协议 | 0x10 扩展HTTP报文结构概念和内容编码
#以后的知识都是HTTP协议的扩展,如果精力有限可以选择暂时忽略,注意只是暂时忽略,以后的东西同样重要 HTTP传输数据时可以直接传输也可以对数据进行编码,由于编码在计算机内运行,所以会占用一些CPU ...
- React组件绑定this的三种方法
我们在使用React组件时,调用方法常常用到this和event对象,默认情况是不会绑定到组件上的,需要特殊处理. 节点上使用bind绑定 特点:该方法会在每次渲染组件时都会重新绑定一次,消耗一定的性 ...