一、简介
上面两篇,我们了解了DDD中战略建模部分。从领域、子域的问题分析,到限界上下文和限界上下文映射图的领域模型划分与集成关系约定。
本篇开始,将开始了解战术部分的内容。即,如何在程序实现的角度进行设计
本文涉及战术内容包括两种领域对象:实体、值对象两种
在OOAD当中,我们通常会通过提取名词的方式来获取一个对象,并获取动词的方式来提取对象的行为。
在DDD当中其实也差不多,根据领域当中的一些领域概念来构建领域对象
二、实体
特征
实体是属于领域对象的一种,它具有两个显著的特征
1、实体是有唯一标识符的,怎么理解这个唯一标识符呢?比如我们都有一个身份证号码,它是唯一的,即使你这个人改了名字,人家依旧根据你的身份证号来标识确定你这个人的唯一性。既然这样,实体在领域模型中存在的意义就是表示那些需要被唯一区分的领域概念。比如,一个商品、一笔订单、一条消息...如此种种
我们可以采用机器算法自己生成ID,也可以通过数据库主键来生成ID,或者从别的地方获取ID,但请记得,在当前的限界上下文当中,这个ID必须是唯一的,至于脱离了当前上下文,那则允许重复。
2、实体是可变的,除了唯一标识外实体是可变的。对于一个唯一的领域对象来说,只要它的唯一标识是相同的,那么它就是同一个领域对象,即使其它属性都不同。也就呼应了唯一标识符这个特征,它确定了对象的唯一性。你不可以改变一个创建的实体的唯一标识符,但是可以改变其中的实体属性。
所以实体在领域模型当中就是为了用来描述具备唯一性的对象,它是领域建模当中描述领域对象很重要的一个概念。
角色
实体描述了一种领域概念,比如一个人,那么这个人可能存在多种角色,假设一个人在公司身兼两职,即是产品经理,又是系统研发人员。
这两种角色拥有不一样的行为,如果我们把它集中用一个实体去描述,那么可能出现一个问题,就是当它以产品经理这个角色出现的时候,我们发现它竟然可以做研发人员这个角色的事。或许在实际工作中是有好处的,但在设计当中这样职责含糊不清的情况只会导致问题的出现。
这时候,我们有两种解决方案
1、我们用两个实体去分别描述产品经理和研发人员,各自实现自己的功能,对于相同的数据只要做拷贝就好了。
2、我们可以把产品经理和系统研发人员设计成两个接口,这样只要我们做向上转型以后,在当前的概念里面也就只有一个角色存在了。
所以,确认实体的时候,我们还得去关注这个实体的角色是什么,这样我们才能适当地去暴露相应的行为,不多也不少。
三、值对象
无唯一标识
值对象也是用来描述领域概念的,和实体最大的不同在于值对象是没有唯一标识的。它只用属性来描述一个领域概念,这意味着当两个值对象的类型和属性相同的时候那么它表示的是同一个领域概念,反之则不是。
不可变
除了没有唯一标识外,跟实体还有一个重要的区别,那就是值对象是一种不可变对象。怎么理解这个不可变呢?我们类比一个Java中的final的概念,即它是一个最终对象。
那如果要修改数据怎么办呢?因为值对象具备不可变性,所以当修改数据的时候不是去修改对象内部的属性,而是将整个对象进行替换。
不可变这个特性可以防止并发场景出现问题,比如你在使用该值对象的过程中却被其它线程修改了属性,由于不可变性,这样的场景可以被杜绝。
无副作用
首先我们思考一个问题,如果值对象包含的方法对值对象内部进行了修改,那么不就破坏了值对象的不可变性吗?
这样调用该方法就会产生副作用,也就是导致不可变性破坏,同时在并发时可以被修改的问题。
而值对象的无副作用就意味着,值对象内部不可以包含修改值对象属性的方法。
四、总结
实体是具备唯一标识,可变的领域对象,用来描述需要被唯一区分的领域概念。
值对象不具备唯一标识,根据对象类型和属性值来确定对象,同时它是不可变的领域对象,它也用来描述领域概念。
那什么时候使用值对象,什么时候使用实体呢?
当你打算新建一个对象的时候,你发现你只需要关心它的属性的时候,那么就使用值对象。
当你觉得需要唯一区分对象的时候,也希望它存活的时间内是可以被修改的,就使用实体。
实体和值对象都是常用的领域对象,它们存在的意义也是从现实世界抽象出来的,也就是我们一直提及的唯一标识、可变性等特性。所以,什么时候使用何种对象,其实依然是根据你的领域概念去思考它具备什么样的特性,然后在建模的时候去选择最恰当的对象。