天天看点

领域驱动设计(DDD):实体

实体

在对实体进行建模时,我们可能会把重点放在实体对象或者唯一标识属性的设计上,然而对于实体的本质并没有一个清晰的概念。这将导致你不知道什么是实体,还要将对象设计成实体的矛盾中。最终可能会为一个模型对象添加一个唯一标识(ID)属性而宣布这个模型对象是个实体对象。

当看到一个计算机领域中的概念,你可能会在意它在计算机领域中的解释。但有很多概念并不是计算机领域所创造的,它可能来源于其它领域。并且计算机领域可能对这个概念并没有做过多的解释,只是对概念的应用。

什么是实体? 当突然被问到这个问题时,你想到了什么?

一个在 E-R 图中所表示的实体类型、一个具有唯一标识的模型、一个被 @Entity 注解标记的模型、一个继承自 Entity 类型的模型、或者把他们融合在一起:一个具有唯一标识的并且被 @Entity 注解标记(或者继承自 Entity 类型)的模型。

这些都只是技术抽象后的实体模型,而并不是实体。那什么是实体呢?

An entity is something that exists separately from other things and has a clear identity of its own.

谷歌翻译:实体是与其他事物分开存在的事物,并且具有自己的明确标识。

抛开计算机领域对你思考上的束缚,你是如何理解实体的呢?

领域驱动设计(DDD):实体

当你带着小朋友去动物园游玩,你指着长颈鹿问小朋友:“这是什么动物呀?”。小朋友会告诉你:“这是长颈鹿。”。你继续问他:“你怎么知道这是长颈鹿的?”,小朋友又告诉你:“因为它的脖子很长,身上还有豹纹。”。你又继续问:鸵鸟、河马、狮子、猴子、大象、...。

小朋友可以通过不同动物的特征来识别和区分动物。人类的思想是可以表达这个世界的,这个可以被识别和区分其他事物的抽象概念被称作:类型(Type)。

类型的出现使得可以更好地表达如何区分事物,这对理解实体以及实体的出现又近了一步。

事物之间可以根据不同类型区分开来了,比如:猴子(鸵鸟、河马、狮子)类。但是如何知道一群猴子里的某一只猴子呢?

小朋友来到小猴子的观赏区,指着一只小猴子说:“猴宝宝”。过了一会那只猴宝宝跳到了一群猴宝宝群里,小朋友此时已经无法找到刚刚那只猴宝宝。

如果可以为每一只猴子分配一个唯一的编号,那么小朋友找到这只猴宝宝就变得十分简单了。 如果小朋友能始终找到这只猴宝宝,那么这个猴宝宝就是一个实体。

准备好,我们要开车了!

对事物的抽象建模称之为实体类型。

对实体类型的实例化称之为实体。

注意实例化的对象是有标识的:

但是非常抱歉的是,我们对事物的认识远远没有那么清晰。

应用逻辑服务(MonkeyService)是一个实体类吗?它显然不是,因为它只是对业务规则的封装,而不是对事物的抽象。他的作用是在执行期间访问各种实体对象来组合业务规则。

只有对事物的抽象才称之为实体。

我们曾经花了很长时间来理解透彻 类(class) 和 对象(object) 之间的关系。

MonkeyService 叫做对象类。

service1, service2 叫做对象。

我们从来没有指着 MonkeyService 这个东西叫对象,service1, service2 这个东西叫类。

正是这样的关系,我们对事物建模形成的类称之为实体类,通过实例化(new)实体类而产生的对象实例称之为实体。

我喜欢称它们为实体对象,因为它们也是个对象。

我还是担心,你没有明白我这么啰嗦的表达什么是实体。我决定从另一个角度在探讨一次什么是实体。

再次强调一下:对事物的建模称之为实体类型。

我们反反复复的强调,对事物的建模就称之为对实体的建模,那什么是事物?什么不是事物呢?

实体(Entity)可以是一个人、一座城市、一辆汽车、一张彩票或一次交易。

我们会使用各种名词来代表事物,比如人、城市、汽车、彩票、交易。因此我们会有一种错觉,对实体的建模就是对名词的建模。然而是这样吗?

我们在创造一个名词时,并不是先创造一个名词然后在指向一种事物。而是存在一种事物,然后在为它创造一个名词。

比如你看到一头猪(动物),你会说:“我看到了一头猪(名词)”。但是这头猪(动物)在没有名词(猪)的时候就已经存在了,在描述没有名词的事物时,你可能会“阿巴阿巴阿巴”的叙述一堆对这个事物的描述。但它却是存在啊,你是通过它所具有的 特质(属性) 来区分这是一头猪不是一头驴的。

在认识事物的时候,你是通过属性来区分不同事物的,而不是最终表现的名词。

事物看起来确实是由这些属性所构成的,那它是由这些属性来确定唯一性的吗?

领域驱动设计(DDD):实体

A:你说的哪一头猪啊?

B:就是那一头啊!

A:那是哪一头?

B:非常胖的那一头!

A:到底哪一头?

B:滚,算了。

那这头没有显式编号(标识)的猪还是不是一个实体呢?

当然是,它必须是。只是你还不知道如何唯一标识它。如果此时你说:“那头编号为 5 的猪。”,这将在描述实体时变得如此简单,因为 A 和 B 都知道 5 号猪是哪一个。就算是不知道,只需要去猪圈里找到 5 号猪即可。

领域驱动设计(DDD):实体

一个事物从产生,发展,兴盛直至消亡的过程中,它们的形式和内容可能一直在发生变化(有可能是根本性的变化)。在经历这个“成住坏空”的过程中什么是不变的呢?

一个人刚出生时可能只有五六斤那么重,成年以后可能会长到一百三四十斤。刚出生时可能只有四五十厘米那么高,成年以后可能会长到一米七八那么高。刚出生时小脸可能有一些红晕,成年以后可能会变得俊美。

在这一生中,你的体型、身高、相貌和年龄都在发生变化,甚至姓名都可能会改变。是什么使你在这茫茫人海中能够独立存在,并且别人能够认出你是谁。甚至在公司里有两个重名的同事,你依然可以分辨他们谁是谁。但是你在通过名字分辨他们时,你脑海里是关联的那个人本身。他就是他,是不会变得。不管他的体型、身高、相貌和年龄发生任何根本变化,你依然能识别他,因为他就是他。

人类需要解决他就是他的这个问题,标识就是为了解决他就是他的这个问题。人类的思考就是这么强悍。

事物本身是具有标识的,只是我们还没有找到如何发现标识的密码。但是我们知道标识是存在的,又无法发现它时,我们只好给它为事物自定义一个标识。

通常标识是不变的,它会伴随一个事物的一生。正是这种不变性与唯一性才能贯穿一个事物在整个生命周期中抽象的连续性。

注意: 在对象模型中,标识是通过字段(属性)的方式进行实现的(表示的),定义一个标识有时只需要一个字段就够了,但有时一个字段往往不能唯一标识一个实体,此时我们可能会使用两个、三个甚至四个字段来明确实体的唯一性,我们把这种多个字段组合成的标识叫做复合标识或者联合标识。

实体必须定义标识吗?

领域驱动设计(DDD):实体

实体必须是有标识的,但是你不一定需要为每一个事物建模时都定义标识。

当你在商场购买一件衣服时,你会在意一件衣服的标识吗?你只会在意它的品牌、尺码、颜色、款式等等一系列的属性,此时商品标识显得就没有那么重要了,商品标识更像是这件衣服的附带品(附加值)。过了几天你又去了这家商场,这次不是买,而是换货。前台服务员让你提供当时够买衣服时的小票,服务员根据小票的标识查询到你购买的衣服,最终为你了换货。

在为小票建模时,小票上的商品标识就无法确定唯一性了,因为多张小票可以关联同一个商品(多对多)。然而这个商品在这一张小票上却是唯一的,我们可以通过(ReceiptId、ProductId)来定位这个购买的商品。但是我们从不关心小票上的商品脱离小票后是否唯一。

小票上的商品是实体吗?

我们在认识实体时特别强调了实体的概念:

实体是与其他事物分开存在的事物,并且具有自己的明确标识。

为一个事物定义了明确的标识后,会使你有一种错觉。你会感觉只要为事物定义明确标识后,事物就是实体或者实体就是事物了。

但是我们对分开存在的理解还需要强化。

分开存在就意味着需要独立存在,小票上的商品对象(Receipt Product)离开小票对象(Receipt)能独立存在吗?

如果能,商品对象(Receipt Product)就是一个实体。

如果不能,商品对象(Receipt Product)就不是一个实体。

如果商品对象(Receipt Product)不是实体,那它是什么?

是对象(Object)啊,这样的回答你是否能想明白(阿巴阿巴阿巴)。我们一直叫实体为实体对象,是因为实体本身就是一个对象啊。不是因为面向对象才叫它对象,而是它就是对象才叫它对象。对象、实体的概念要比计算机对面向对象的应用早的多得多。

对象要比实体的范围大的多得多,对象包括(实体),实体对象只是对实体的抽象,对象比实体抽象的“东西”多得多的。

建模:实体与值对象,做出选择。

领域驱动设计(DDD):实体

“我们应该尽量使用值对象来建模而不是实体对象”,我在看到这句话时,确实惊讶不已。

阿巴阿巴阿巴,去做选择吧。

对象是由属性和方法组成。

我们都在试图理解这句话,有的时候却一直在相半而行。

过去我们的对象上只包含属性,后来我们意识到这样是不完整的。然后我们开始为对象抽象方法,添加方法。但是这个过程却发生了极化,有一部分人提出只能使用方法来操作对象。

这个过程最终产生了三种方式来操作对象:

只使用属性来操作对象。

使用属性和方法来操作对象。

只使用方法来操作对象。

这三种方式到底哪个合理呢?

在《Java 编程思想》中有一个使用电灯(Light)的例子来初次解释什么是面向对象。

领域驱动设计(DDD):实体

这个例子为我们展现了电灯对象所具有的行为:开灯(On)、关灯(Off)、变亮(Brighten)、变暗(Dim),这些行为是本身所具有的。

到目前为止这个例子好像确实是只使用方法来操作对象。

如果现在要求可以任意调整电灯的颜色(Color),应该怎么设计了呢?

颜色(Color)是一个属性(Property),需要改变电灯的颜色。我们瞬间想要为电灯对象提供一个改变颜色(Change Color)的方法。

真的需要为电灯对象提供一个改变颜色(Change Color)的方法吗?

不一定需要,直接修改属性(Property)是可以的。

你知道属性(Property)和字段(Field)的区别?

属性(Property)具有封装性。

有一天你感觉那面墙的颜色有些不好看,你拿着刷子就去刷墙。墙也没有为你提供一个改变颜色的方法,而你却直接改变了墙的颜色属性。

这个方法属于这个对象吗?

我曾经看到过一个视频:外国人用白瓷盘切烤制好的乳猪,完成切割后随手抛出瓷盘摔坏。

你觉得摔坏这个动作属于盘子对象吗?如果属于:

这就好比,你对这一个盘子告诉它,你摔坏自己。盘子本身明显是不具备自我摔坏这样的功能的,盘子之所以会被摔坏是外界对它的摧残。对于实体对象,本身方法应该是对事物本身所具有功能的抽象。

由于在 Java 中不能直接创建函数(function),所以你只能这样:

Mallfoundry 是一个完全开源的使用 Spring Boot 开发的多商户电商平台。它可以嵌入到已有的 Java 程序中,或者作为服务器、集群、云中的服务运行。

领域模型采用领域驱动设计(DDD)、接口化以及面向对象设计。

项目地址:https://gitee.com/mallfoundry/mall

我们从认识实体中了解到了实体的概念。并在形成实体中去引出事物与实体的关系,实体类与实体的关系,类与对象的关系,以及实体与对象的关系。又在实体标识中强调了标识对实体的重要性,以及由强调了实体与对象的关系。最后又加入了一点对实体行为的扩展,对实体本身的属性和方法做了一点点的探讨。

请记住这句话:实体是与其他事物分开存在的事物,并且具有自己的明确标识。