JavaBean, POJO, DTO, VO, 傻傻分不清楚?

关于这些概念,网上有很多解释,大多数都是胡说八道。我在 Stack Overflow 上看到了一个版本,认为这个理解是比较合理的。

太长不看版:

DTO 和 VO 用途是一样的,大多数语境下都可以互相替换。JavaBean 是一个惯例,DTO 和 VO 都遵循了这个惯例,所以他们都是 JavaBean. DTO, VO 和 JavaBean 都是 POJO.

JavaBean

Sun 推出了一个 JavaBean 的惯例,遵循了这个惯例的 Java 对象都是 JavaBean. 所以首先要理解的是,JavaBean 并不是一个实现,也不是接口,只是一个惯例。

这些惯例包括:

  • 可以使用一个没有参数的构造器初始化出来这个 Bean;
  • 可以通过 getter/setter 读写 Bean 的属性;
  • 可以序列化这个 Bean;
  • …… (详细的定义请参考 Sun 的说明)

Java语言欠缺属性、事件、多重继承功能。所以,如果要在Java程序中实现一些面向对象编程的常见需求,只能手写大量胶水代码。Java Bean正是编写这套胶水代码的惯用模式或约定。这些约定包括getXxx、setXxx、isXxx、addXxxListener、XxxEvent等。遵守上述约定的类可以用于若干工具或库。

——来源:杨博

有了这个概念(惯例),Spring, Hibernate 这些框架交流、实现起来,都大量使用 Bean 这个概念。比如“注入一个 Bean“,“声明一个 Bean”,你就知道这里的这个 Bean 必须要有无参数的构造函数,必须要有 setter/getter 等等。这些框架在使用的时候,会采用初始化出来 Bean 然后 setXX() 这种方式,构造出来最终的 Bean.

JavaBean 并不是一个接口,更像是为了交流方便的一个名词。

POJO

POJO 的全称是 Plain Old Java Object, 简单又老的 Java 对象。这里的简单是相对来讲的。 EJB 2.x 的 Entity Beans 比较重量,需要实现 javax.ejb 的一些接口。而 POJO 就比较轻量,就是一个 Java 对象,不需要实现任何的接口。

这个术语由 Martin Fowler, Rebecca Parsons 和 Josh MacKenzie 在 2020 年提出:

我们想,为什么人们不愿意使用一个普通的对象,大概是因为普通的对象没有一个 fancy 的名字,所以我们给它们取了一个。

所以 POJO 本质上也是可以方便沟通的术语。PO 在那个年代也不是一个新词:

  • 电话制造行业的 POTS, Plain Old Telephone Service
  • PODS, Plain Old Data Structures, 表示在 C++ 定义数据结构,但是只使用 C 语言里面的 Feature
  • POD, Plain Old Documentation, Perl 语言中的概念

有了 POJO 这个名字,相比框架里面各种的对象概念,就容易理解多了,所以这个概念被很广地使用开来。可以用 POJO 来解释 JavaBean: JavaBean 就是可以序列化的 POJO, 并且有无参构造器,可以使用 getter/setter 来读写属性。

VO

Value Object, 是保存了值的对象。比如 java.lang.Integer. Martin Fowler 对 Value Object 的描述是:

Value Object 是指像 Money 或者 Date Range Object 这样的小对象。对它们来说,值的语义比 id 的语义更加重要。比较它们相等的时候,只要它们的值相等就可以认为是相等,而不需要在意是不是一定是同一个对象。

一个比较好理解的视角是,Value Object 应该是不可变的。如果你要改变一个 Value Object, 你应该创建一个新的然后替换掉原来的,而不应该改变原来的对象的属性。

我认为这个上面这个对 Value Object 的理解是比较合理的,但是早期的 J2EE 教材将 Value Object 描述为一个完全不同的理解,在这个理解中 VO 完全等价于 Data Transfer Object.

DTO

Data Transfer Object. Data Transfer Object 是一个 EJB 中引入的(反)设计模式。思想是将很多数据组装在一个 Value Object 中,然后通过网络传输。而不是在 EJB 中进行很多次远程调用。

DTO 和业务对象的区别是:DTO 除了保存数据,没有其他的行为(方法)。

传统中的 EJB 引入 DTO 主要是为了解决两个问题:

  1. EJB 的 Entity Beans 无法序列化,DTO 解决这个序列化的问题;
  2. DTO 的使用,含蓄的引入了一个组装数据的过程,将所有的数据组装好再返回;

总结一下,对于大多数的情况, DTO 和 VO 是一个东西(Martin Fowler 理解的 VO 不是),并且它们遵守了 JavaBean 的约定。所有的这些东西都是 POJO.

我觉得本质上讲,这些概念也是类似一种“设计模式”的东西,之所以提出来这些概念,还是解决 Java 表达能力的不足,需要用一种通用的做法在 Java 程序员中形成共识,来解决这个问题。比如在 RPC 调用中,我定义了这么一个方法:

然后被同事说不能这么用,这样“没有扩展性”,假如将来要添加一个新的字段 address ,那么你这个接口扩展了一个字段,就不能向后兼容了。

应该这么定义:

这样,这个接口就有扩展性了。实际上,这也是 JavaBean 的一个用法吧。

但是在 Python 中,我们的函数的变量的形式有:

处理起来“兼容”问题就简单多了。

但是 Java 不行,Java 必须将参数封装成对象,然后生成一堆 setter/getter,原本一行代码能搞定的事情,就要写成几十行。

相关参考:

  1. 阿里巴巴Java开发手册中的DO、DTO、BO、AO、VO、POJO定义
  2. https://stackoverflow.com/a/1612671/6931919


JavaBean, POJO, DTO, VO, 傻傻分不清楚?”已经有2条评论

  1. > 然后被同事说不能这么用,这样“没有扩展性”,假如将来要添加一个新的字段 address ,那么你这个接口扩展了一个字段,就不能向后兼容了。

    那就加个 Result register(String username, Integer age, String address); 接口不行?

    • 理论上是可以,用 java 的重载来向后兼容嘛。但是说服不了同事啊,他们对外的接口全都写成 Request 对象的。

Leave a comment

您的电子邮箱地址不会被公开。 必填项已用 * 标注