什么是实体?
实体,官方的解释是:实体(Entity,又称为Reference Object)很多对象不是通过他们的属性定义的,而是通过一连串的连续事件和标识定义的。主要由标识定义的对象被称为ENTITY。
但,这官方的解释,反而将实体玄奥起来了。
很书籍、博客上说,实体一定是具有唯一标识(ID),于是很多人便认为,有唯一标识的就是实体。然而,值对象,也可以有唯一标识。
也有人说,实体与数据库表一一对应,那么,只要是要与数据库做映射的,都是实体。然而,值对象,也可以与数据库表映射。
其实,实体的定义很简单:
实体是有生命周期,能演变,里面的属性值可以随时变化。简单的说,实体有类似set方法,可以在任何时候修改实体的属性。
只要是符合这个本质的,都可以称之为实体。
实体的特征
1、具有唯一标识(世界上没有相同的两片绿叶)
2、具备生命周期,在生命周期内其内部属性可以被改变
3、实体具有行为方法
DDD为啥要提出实体这个概念?
试想一下,我们传统开发的时候,是怎么进行建模的?
我相信,大多团队,都是直接在数据库里面建一张表,就完事了吧。
好一点的团队,会使用PowerDesigner之类的工具构建出物理模型。
再好一点的团队,会构建出逻辑模型,然后通过逻辑模型再生成物理模型。但更多的人会认为逻辑模型是个多余,因为项目一个确定,所使用的数据库就基本已经确定,直接设计物理模型,不香么?
再回想,以上的这些方式设计出来的模型,是不是基本上只有开发人员在参与?因为,业务人员也看不懂这些。
再回想,只有开发人员参与得出的模型,真的是符合业务的模型吗?真的是符合实现世界的模型吗?
很可惜,只有开发人员参与得出的模型,往往只能满足当时的业务需求,一当业务需求发生改变时,这模型便不符合了,严重的,会导致整个项目需要大重构才能满足新的业务需求。
难怪,会有如此多的项目推翻重构,从1.0系统到5.0系统,每一次都是推翻前者进行重构开发。
实体是模拟真实的物理世界,是连接开发人员与业务人员的重要工具。
DDD提倡的是用模型来模拟现实的物理世界,只有符合现实物理世界的模型才是好的模型。
在DDD中,实体与值对象是领域进行建模的重要工具。
实体与贫血模型、充血模型
在实体中,如果只有简单的get、set方法,那么实体表现就是贫血模型。如果实体中有着丰富的行为方法,那么实体表现就是充血模型。
很多DDD的书,都推荐使用充血模型,而反对贫血模型。然而,在工程实践中,往往发现贫血模型更加好用,充血模型使代码看上去臃肿不堪。
首先,需要申明的是,实体与数据库不是映射关系!实体的持久化,不一定得持久到到数据库中,实体可以持久化到文件中、缓存中、甚至内存中!!
而PO(persistant object)才是与数据库一一映射的对象。
在PO中适合贫血模型,在实体中适合充血模型。实体与PO,只通过资源库关联起来。
实体的持久化是通过资源库而实现的,而资源库的具体实现,则是通过依赖倒置和依赖注入的方式在基础设施层实现,PO放在基础设施层与数据库一一映射。实体中丰富的行为方法,操作着资源库对象,通过资源库对象操作着PO对象,直到将数据写入数据库。
如何践行实体?
物理世界中的每一个物体、每一个概念,都与一个实体一一对应。
比如,在线上图书馆中,书籍得对应书箱实体。书籍有编号(唯一标识)、书名、作者、出版社、出版时间、售价、封面、内容等属性,有借出、归还、上架、下架等动作。书籍的生命周期是入库到出库时间段。在物理世界中的书籍对应数字世界中的书籍实体,也是具有和物理世界一样的属性、动作、生命周期。
实体的建模,可以由业务人员来完成,也可以由开发者与业务人员一起完成。
设计实体的工具,可以用简单的Excel表格,也可以用专业的UML软件,甚至,可以在白纸上画一些简单的示意图!
注意的是,实体的唯一标识,不一定是数据库的主键,虽然很多实际上的做法,数据库主键都是等于实体的唯一标识。
注意的是,如果一个物理世界的事物,更适用使用值对象来建模,那么,请使用值对象。DDD的原则是,能用值对象尽量用值对象,不能用值对象时再考虑实体。
注意的是,实体是处于设计阶段,不要急迫地把实体变成代码!因为,你设计好的实体,需要与业务人员一起审核,在审核过程中,业务人员会提出他的问题,指出模型中的缺陷,或者增加一些模型。
注意的是,如果一个实体在真实世界中找不到对照,那么这个实体就肯定是有问题的,需要考虑是否要把实体删除。
import org.ddd.book.domain.book.repository.IBookRepository;
import org.ddd.book.domain.book.vo.AuthorVO;
import org.ddd.book.domain.book.vo.PublisherVO;
import org.ddd.book.domain.factoty.RepositoryFactory;
import java.util.Date;
/**
* 书籍实体
*/
public class BookEntity {
// 书籍ID(编号)
private Long bookId;
// 书籍名称
private String bookName;
// 作者
private AuthorVO author;
// 出版社
private PublisherVO publisher;
// 出版时间
private Date publishDate;
// 售价
private Long price;
// 封面
private String headImg;
// 内容
private BookContentEntity bookContent;
// 持久化
public void save() {
RepositoryFactory.get(IBookRepository.class).save(this);
}
// 上架
public void down() {
}
// 下架
public void up() {
}
// 借出
public void lend() {
}
// 归还
public void returnBack() {
}
}
了解更多,请关注公众号:jgssy01