实体类的主要职责是存储和管理系统内部的信息,它也可以有行为,甚至很复杂的行为,但这些行为必须与它所代表的实体对象密切相关。即实体类有两方面内容:存储数据和执行数据本身相关的操作。这两方面内容对应到实现上,最简单的实体类就是POJO类,只含有属性及其对应的set/get方法。实体类常见的方法还有用于输出自身数据的toString方法。
领域模型中的实体类可以分为四种类型:VO、DTO、DO、PO,它们分别用于不同业务层次间的交互,并会在层次内实现实体类之间的转化。
VO:视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。VO 只包含前端需要展示的数据即可,对于前端不需要的数据,不应该在VO中体现。通常遵守 Java Bean 的规范,拥有 getter/setter 方法。
DTO:数据传输对象,这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,我泛指用于展示层与服务层之间的数据传输对象。通常遵守 Java Bean 的规范,拥有 getter/setter 方法。
DO:领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。一般和数据库中的表结构相对应的。
在项目开发中,VO对应于页面上需要显示的数据(表单),DO对应于数据库中存储的数据(数据表),DTO对应于除二者之外需要进行传递的数据。
PO:持久化对象,它跟持久层(通常是关系数据库)的数据结构形成一一对应的映射关系(用于表示数据库中的一条记录映射成的 java 对象),如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。PO 仅仅用于表示数据,没有任何数据操作。通常遵守 Java Bean 的规范,拥有 getter/setter 方法。
DAO:数据访问对象。主要用来封装对数据库的访问。通过它可以把POJO持久化为PO,用PO组装出来VO、DTO。使用DAO访问数据库,通常与PO结合使用,DAO中包含了各种数据库的操作方法。DAO一般在持久层,通过它的方法,结合PO对数据库进行相关的操作。夹在业务逻辑与数据库资源中间,配合VO,提供数据库的CRUD操作。
开发中如果整个业务过程的各层之间都是通过DO类对象进行数据传递的,其出现的问题是:
不需要的字段也暴露到前端
某些字段其实是需要转换后才显示到前端的,但现在无法支持了
某些字段需要在前端展示,但又不希望出现在数据库中
还有很多其他问题会暴露
业务分层为:视图层(VIEW+ACTION)、服务层(SERVICE)、持久层(DAO)三层架构。相应各层间实体的传递如下图:
一个POJO持久化之后就是PO;直接用它传递,传递过程中就是DTO;直接用来对应表示层就是VO。即传递过程同时也是一个对象映射转换的过程(这里的对象映射转换就是另外一个概念,如使用工具包Dozer)。
为什么要专门定义DTO来绑定表现层中的数据而不直接用实体模型?
表现层(UI)与应用服务层(Service)之间是通过数据传输对象(DTO)进行交互的,数据传输对象是没有行为的POJO对象,它的目的只是为了对领域对象(DO)进行数据封装,实现层与层之间的数据传递。为何不能直接将领域对象用于数据传递?因为领域对象更注重领域,而DTO更注重数据。不仅如此,由于“富领域模型”的特点,这样做会直接将领域对象的行为暴露给表现层。
需要了解的是,数据传输对象DTO本身并不是业务对象。数据传输对象是根据UI的需求进行设计的,而不是根据领域对象进行设计的。比如,Customer领域对象可能会包含一些诸如FirstName, LastName, Email, Address等信息。但如果UI上不打算显示Address的信息,那么CustomerDTO中也无需包含这个 Address的数据
简单来说Model面向业务,我们是通过业务来定义Model的。而DTO是面向界面UI,是通过UI的需求来定义的。通过DTO我们实现了表现层与Model之间的解耦,表现层不引用Model,如果开发过程中我们的模型改变了,而界面没变,我们就只需要改Model而不需要去改表现层中的东西。
大多数情况下,DTO和VO的属性值基本是一致的,且都是POJO,那为什么还需要VO呢?