浅析领域驱动模型VO、DTO、BO、PO 等的概念、区别及其用法

目录
  1. 1. 名词解释
  2. 2. POJO
  3. 3. VO
  4. 4. DTO
  5. 5. Entity/PO
  6. 6. DO
  7. 7. BO
  8. 8. Model
  9. 9. View
  10. 10. DAO
  11. 11. 放在哪个目录
  12. 12. POJO的扩展
  13. 13. 易混点一:VO和DTO
  14. 14. 易混点二:BO和PO
  15. 15. 易混点三:BO和DTO

名词解释

领域模型中的实体类分为四种模型:VO、DTO、DO和PO,各种实体类用于不同业务层次间的交互,并会在层次内实现实体类之间的转化。新项目使用了新的框架和开发规范,特意集体讨论了DTO,DO,VO,BO,POJO,PO和Entity以及DAO、Model和View的基本概念和使用场景,为了深入理解,这里整理为一篇笔记。

下面通过一张图看一下它们的大致分类:

用MVC模式的角度接着看图,看完图估计大部分人对这些专业术语就有一个直观的感受了:

POJO

总的来说,普通Java对象POJO(Pure Old Java Object 、 Plain Ordinary Java Object),按照Martin Fowler的解释是“Plain Old Java Object”,从字面上翻译为“纯洁老式的java对象”,但大家都使用“简单java对象”来称呼它。包含DO、DTO、BO、VO和PO等,它们本质上都是一个简单的java对象,实际就是普通的JavaBeans。没有业务逻辑,有时可以作为VO或DTO来使用。当然,这里特意说明纯普通Java对象,如果你有一个简单的运算属性也是可以的,但不允许有业务方法。

POJO是指这样的java对象:

  • 有一些private的参数作为对象的属性
  • 针对每一个参数定义get和set方法
  • 没有从任何类继承
  • 没有实现任何接口
  • 没有被其它框架侵入。

许多开发者把JavaBean看作遵从特定命名约定的POJO。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class User {
private Long id;
private String userName;
private String msg;
private Integer age;
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}

POJO其实是比javabean更纯净的简单类或接口。POJO严格地遵守简单对象的概念,而不具有业务逻辑处理的能力,而一些JavaBean中往往会封装一些简单逻辑。例如,改造User后,可以得到一个JavaBean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import java.io.Serializable;

public class User implements Serializable {
//实现serializable接口
private static final long serialVersionUID = -2241142936329900646L;
private Long id;
private String userName;
private String msg;
private Integer age;

/**
* 无参构造器
*/
public User() {
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public User(Long id, String userName, Integer age) {
this.id = id;
this.userName = userName;
this.age = age;
}

}

VO

VO(View Object)视图模型,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。如果是一个DTO对应一个VO,则DTO等价于VO;但是如果一个DTO对应多个VO,则展示层需要把VO转换为服务层对应方法所要求的DTO,传送给服务层,从而达到服务层与展示层解耦的效果。

一般用在业务逻辑层(Service)对前端(Web) 的视图模型效果控制的展示上,说白了就是后台向前端传输数据。示例:xxxVO,xxx一般为网页名称。注:在展示业务不复杂的系统,可直接使用DTO。

DTO

数据传输对象DTO(Data Transfer Object)是一个比较特殊的对象,它有两种存在形式:在后端,它的存在形式是java对象,也就是在controller里面定义的请求参数,通常在后端不需要关心怎么从json转成java对象的,这个都是由一些成熟的框架帮你完成啦,比如spring框架;在前端,它的存在形式通常是js里面的对象(也可以简单理解成json),即通过ajax请求的那个数据体。这也是为什么把他画成横跨两层的原因。举个例子,xxxDTO,xxx为业务领域相关的名称。

现在微服务盛行,服务和服务之间调用的传输对象能叫DTO吗?我的理解是看情况,DTO的一个隐含意义是要能够完整的表达一个业务模块的输出。如果服务和服务之间相对独立,那就可以叫DTO;如果服务和服务之间不独立,每个都不是一个完整的业务模块,拆开可能仅仅是因为计算复杂度或者性能的问题,那这就不能够叫做DTO,只能是BO。

DTO与BO或者DO的区别是DTO没有任何行为(方法),只是存储和提供它所拥有数据的查询(访问器和修改器)。DTO是简单对象,不包含任何需要测试的业务逻辑。

Entity/PO

持久化对象PO(Persistent Object)等同于Entity,它们的概念是一致的。数据库表中的记录在java对象中的显示状态。最形象的理解就是一个PO对象对应数据库中的一条记录,一个PO的数据结构对应着库中一张表的表结构,即自身属性与数据表字段一一对应。好处是可以把一条记录作为一个对象处理,方便的转为其它对象。

例如我们有一条数据,现在有一个简单类而且已经是被赋予了这条数据的实例,那么这条数据在这个简单类中的存在状态就是PO,不管这个简单类是DO还是BO抑或其它。PO只是数据持久化的一个状态。

通常PO里面除了get,set之外没有别的方法。对于PO来说,数量是相对固定的,一定不会超过数据库表的数量。

DO

领域对象 DO(Domain Object) 是从现实世界中抽象出来的有形或无形的业务实体,它用来接收数据库对应的实体,是一种抽象化的数据状态,介于数据库与业务逻辑之间。

一般在业务逻辑层(Service)对数据库(SQL) 进行访问时,用于接收数据。xxxDO,xxx即为数据表名。另外,DO与Entity的不同点就是DO是与数据库存在着某种映射关系的Entity,总的来说DO是Entity的一种。

现在主要有两个版本一个是阿里巴巴的开发手册中定义的DO( Data Object),这个等同于上面的PO;另一个是在DDD(Domain-Driven Design)领域驱动设计中定义的DO(Domain Object),这个等同于上面的BO。

BO

业务对象(Business Object,BO)是对数据进行检索和处理的组件,主要作用是把业务逻辑封装为一个对象,这个对象可以包括一个或多个其它的对象。形象描述为一个对象的形为和动作,当然也有涉及到其它对象的一些形为和动作。

BO通常位于中间层或者业务逻辑层。BO支持序列化和反序列化,可以轻易地将BO的Java实例转换为一个XML文件或者一个流保存起来,并且在需要的时候,将这个BO从XML或者流中转换回一个Java实例。举个简单的例子,一个简历包含教育经历、工作经历、社会关系等三个模块,每个模块对应一个PO;建立一个BO对象处理简历,则每个BO包含这三个PO。

应用中的所有实体(Entity)都是BO,但并不是所有BO都是实体。BO包括包含方法的实体对象(Entity Object)和不包含方法的值对象(VO)。

Model

Model是数学逻辑名词,包括有限操作的集合以及定义于其上的关系,主要用于分析、设计过程。

实体类和模型Model在计算机程序设计中有两个概念:一个是三层架构中的实体类,另一个是MVC架构中的模型。在“三层架构”中,为了面向对象编程,将各层传递的数据封装成实体类,便于数据传递和提高可读性。在MVC(模型Model-视图View-控制器Controller)模式中,Model代表模型,是业务流程/状态的处理以及业务规则的制定,接受视图请求的数据,并返回最终的处理结果。业务模型的设计可以说是MVC最主要的核心。

View

在MVC模式中,View代表视图,用来解析Model带来的数据模型,以展示视图数据,View的模型觉决定了需要什么样的Model来对接,相互联系。

DAO

数据访问对象DAO (Data Access Object)是一个数据访问接口,所谓数据访问,顾名思义,就是与数据库打交道,夹在业务逻辑与数据库资源中间。

一般在业务逻辑层对数据库进行访问时使用。

xxxDAO,xxx即为实体类名(Entity实体)。在核心J2EE模式中是这样介绍DAO模式的:为了建立一个健壮的J2EE应用,应该将所有对数据源的访问操作抽象封装在一个公共API中。用程序设计的语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候就使用这个接口,并且编写一个单独的类或者xml文件,来实现这个接口在逻辑上对特定数据的操作。

放在哪个目录

  • PO通常放在名为bean、entity、model目录中。
  • DAO通常在DAO、mapper目录中。
  • BO通常在service、manager、business目录中。

POJO的扩展

POJO仅包含最简单的字段属性,没有多余的东西,它本质上就是一个普通的JavaBean。但是在POJO的基础上,能够扩展出不同的对象。

  • 为POJO增加了持久化的方法(Insert、Update、Delete……)之后,POJO就变成了PO。
  • 为POJO增加了数据绑定功能之后,POJO就变成了View Object,即UI Model。
  • 为POJO增加业务逻辑的方法(比如单据审核、转帐……)之后,POJO就变成了Domain Model。
  • POJO还可以当作DTO使用。

易混点一:VO和DTO

首先VO是最常用的,但对于这个概念,网上也是众说纷纭,value object 或 view object,一般说视图对象或者值对象,我更倾向理解为视图对象。说白了它就是展示用的,不管展示方式是网页,还是客户端,还是APP,只要是这个东西是让人看到的,我们就把它封装为VO。

VO比较容易混淆的是DTO,DTO是展示层与服务层之间传递数据的对象,可以这样说,对于绝大部分的应用场景来说,DTO和VO的属性值基本是一致的,而且他们通常都是POJO,那么既然有了VO,为什么还需要DTO呢?

我们举例来说明一下:

某公司有一个后台服务,服务层有一个getUser的方法返回一个系统用户,包含sex(性别)、年龄。对于服务层来说,DTO只从语义上定义,可能是这样的:

1
{ "gender":"男", "age":35}

但这个服务同时供多个客户端使用(不同门户),而不同的客户端对于表现层的要求有所不同,比如管理端要求显示准确的年龄,而应用端为了保护客户隐私,只需要显示一个年龄段即可。

管理端VO:

1
{ "gender":"男", "age":35}

应用端VO:

1
"gender":"男""age":30~40}

从这个例子可以看出,DTO很有存在的必要,根据职责单一原则,服务层只负责业务,与具体的表现形式无关,DTO不应该出现与表现形式的耦合,DTO定义的是原始数据,VO再对DTO数据进行解释。这下VO和DTO用法就清晰很多了。

易混点二:BO和PO

PO是持久对象,这个很好理解,就是实体和数据库字段的对应,一个PO的数据结构对应着库中表的结构,表中的一条记录就是一个PO属性,大多数情况下,PO仅仅作为PO只是用来增删改使用。

PO比较容易混淆的是BO,BO是业务对象,对应的是某个具体的业务块,可以包含多个属性、对象。简单点来说,我们可以把BO看作是PO的组合。

我们举例来说明一下:

PO-1是交易记录对象,PO-2是登录记录对象,PO-3是商品浏览记录对象,PO-4是添加购物车记录对象,PO-5是搜索记录对象,BO是个人网站行为对象,BO对象:{PO-1;PO-2;PO-3;PO-4;PO-5}。这样做的优点不言而喻,维护代码的时候查看BO,就能知道这块逻辑涉及多少表(PO)。

易混点三:BO和DTO

搞清楚了BO和PO各自的用途后,我们会发现BO和DTO有重叠功能,一样可以对PO进行排列组合,那BO的存在的意义是什么呢?

从用途上进行根本的区别,BO是业务对象,DTO是数据传输对象,虽然BO也可以排列组合数据,但它的功能是对内的,比如上个例子中的BO对象包括{PO-1;PO-2;PO-3;PO-4;PO-5}还有其他字段属性,但在提供对外接口时,BO对象中的某些属性对象可能用不到或者不方便对外暴露,那么此时DTO只需要在BO的基础上,抽取自己需要的数据,然后对外提供。

在这个关系上,通常不会有数据内容的变化,内容变化要么在BO内部业务计算的时候完成,要么在解释VO的时候完成。