VO, DTO, DO, PO四胞胎傻傻分不清楚?那快来看这篇文章!

news2025/1/9 17:07:14

概念

  • VO(View Object)

视图对象,用于展示层,作用是把某个指定页面(或组件的所有数据封装起来)。

  • DTO(Data Transfer Object)

数据传输对象,这个概念来源于J2EE的设计模式,原来的目的是为了给EJB(Enterprise Java Bean, 企业Java Bean)的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,泛指用于展示层和服务层之间的数据传输对象。

  • DO(Domain Object)

领域对象,就是从现实世界中抽象出来的有形或者无形的业务实体。

  • PO(Persistent Object)

持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对象的映射关系,如果持久层是关系型数据库,那就数据表中每个字段就对应PO的一个(或若干个)属性。

模型

下面以一个时序图建立简单模型来描述上述对象在三层架构应用中的位置
在这里插入图片描述

  • 用户发出请求,表单的数据在展示层被匹配为VO。
  • 展示层把VO转换为服务层对应方法所要求的DTO,传送给服务层。
  • 服务层首先根据DTO的数据构造一个DO,调用业务方法完成具体业务。
  • 服务层把DO转换为持久层对应的PO(可以使用ORM工具,也可以不用),调用持久层的持久化方法,把PO传递给它,完成持久化操作。
  • 对于一个逆向操作,如读取数据,也是用类似的方式转换和传递。

区别分析

VO与DTO的区别

可能存在一个疑问是,既然DTO是展示层与服务层之间传递数据的对象,为什么还需要一个VO呢?对于绝大部分的应用场景来说,DTO和VO的属性基本是一致的,而且它们通常都是POJO(Plain Ordinary Java Object),因此没必要多此一举,但这是实现层面的思维,对于设计层面来说,概念上还是存在VO和DTO,因为两者有着本质上的区别,DTO代表服务层需要接受的数据和返回的数据,而VO代表展示层需要展示的数据。

用一个例子来说明可能会比较容易理解:例如服务层有一个getUser的方法返回一个系统用户,其中有一个属性是gender,对于服务层来说,它只从语义上定义:1-男性,2-女性,0-未定义;而对于展示层来说,可能需要用到“帅哥”代表男性,用“美女”代表女性,用“秘密”代表未指定。说到这里,可能还有疑问,在服务层就直接返回“帅哥美女”不就行了吗?对于大部分应用来说,这不是问题,但设想一下,如果需求允许用户可以定制风格,而不同风格对于“性别”的表现方式不一样,又或者这个服务同时供多个客户端使用,而不同的客户端对于表现层的要求有所不同,那么问题就来了。再者,回到设计层面上分析,从职责单一原则来看,服务层只负责业务,因此,它返回的DTO,不应该表现出与表现形式的耦合。

VO和DTO的应用

不一定非要区分VO和DTO,以下场景可以考虑把VO和DTO二合为一(实现层面上来说):

  • 当需求非常清晰稳定,而且客户端很明确只有一个时,没有必要非要把VO和DTO区分开来,这时候VO可以退隐,用一个DTO即可。为什么时VO退隐而不是DTO?回到设计层面,服务层的职责依然不应该与展示层耦合,所以,对于前面的例子,DTO对于性别来说,依然不能用“帅哥美女”,这个转换应该依赖与页面的脚本(JS)或其他机制(JSTL,EL,CSS)
  • 即使客户端可以定制,或者存在多个不同的客户端时,如果客户端能够用某种技术(脚本或其他机制)实现转换,同样可以让VO退隐。

以下场景需要优先考虑VO,DTO并存:

  • 上述场景的反面场景
  • 因为某种技术原因,比如某个框架(如Flex)提供自动把POJO转换为UI中的某些Field时,可以考虑在实现层面定义出VO,这个权衡完全取决于使用架构的自动转换能力带来的开发和维护雄安率提升与设计多一个VO所多做的事情带来的开发和维护效率的下降之间的对比。
  • 如果页面出现一个“大视图”,而组成这个大视图的所有数据需要调用多个服务,返回多个DTO来组装。

DTO与DO的区别

首先是概念上的区别,DTO是展示层和服务层之间的数据传输对象(可以认定为是两者之间的协议),而DO是对现实世界各种业务角色的抽象,这就引出了两者在数据上的区别,例如UserInfo和User,对于一个getUser方法而言,本质上它永远不应该返回用户的密码,因此UserInfo至少比User少一个password的数据。而在领域驱动设计中,DO不是简单的POJO,它具有领域业务逻辑。

DTO和DO的应用

在上一节的例子中,可能会有一个疑问:既然getUser方法返回的UserInfo不应该包含password,那么就不应该存在password这个属性定义,但如果同时有一个createUser方法,传入的UserInfo需要包含用户的password,怎么办?在设计层面,展示层向服务层传递的DTO与服务层返回给展示层的DTO在概念上是不同的,但是在实现层面,通常很少这样做(定义两个UserInfo),甚至更多,因为这样做并不见得很明智,我们完全可以设计一个完全兼容的DTO,在服务层接收数据的时候,不该由展示层设置的属性,无论是否设置,服务层都一概忽略,而在服务层返回数据时,不该返回的数据(如用户密码),就不设置对应的属性。

对于DO来说,还有一点需要说明:为什么不在服务层直接返回DO呢?这样可以省去DTO的编码和转换工作,原因如下:

  • 两者在本质上的区别可能导致彼此并不一一对应,一个DTO可能对应多个DO,反之亦然,甚至两者存在多对多的关系。
  • DO具有一些不应该让展示层知道的数据。
  • DO具有业务方法,如果直接把DO传递给展示层,展示层的代码就可以绕过服务层直接调用它不应该访问的操作,对于基于AOP拦截服务层来进行访问的控制的机制来说,这问题尤为突出,而在展示层调用DO的业务方法也会因为事务的问题,让事务难以控制。
  • 对于某些ORM框架来说,通常会采用”延迟加载“技术,如果直接把DO暴露给展示层,对于大部分情况,展示层不在事务范围之内,如果其尝试在Session关闭的情况下获取一个未加载的关联对象,会出现运行时异常。
  • 从设计层面来说,展示层依赖与服务层,服务层依赖于领域层,如果把DO暴露出去,就会导致展示层直接依赖领域层,这虽然是单向依赖,但这种跨层依赖会导致不必要的耦合。

对于DTO来说,也有一点必须要进行说明,就是DTO应该是一个”扁平的二维对象“,举个例子来说明:如果User会关联若干个其他实体(例如Address,Account,Region等),那么getUser返回的UserInfo,是否需要把其关联对象的DTO都一并返回呢?如果是这样的话,必然会导致数据传输量的大增,对于分布式应用来说,由于涉设数据在网络上的传输、序列化和反序列化,这种设计更不可接受。如果getUser除了要返回User的基本信息外,还需要返回一个AccountId、AccountName、RegionId、RegionName,那么请把这些属性定义到UserInfo中,把一个”立体“的对象树”压扁“成一个”扁平的二维对象“。

DO和PO的区别

DO和PO在绝大部分情况下是一一对应的,PO是只含有get/set方法的POJO,但在某些场景下还是能反映出两者在概念上的本质区别:

  • DO在某些场景下不需要进行显式持久化,例如利用策略模式设计的商品折扣策略,会衍生出折扣策略的接口和不同折扣策略的实现类,这些折扣策略实现类可以算是DO,但是它们只驻留在静态内存,不需要持久化到持久层,因此,这类DO是不存在对应的PO的。
  • 同样的道理,在某些场景下,PO也没有对应的DO,例如教师Teacher和学生Student存在多对多的关系,在关系数据库中,这种关系需要表现为一个中间表,也就对应有一个TeacherAndStudentPO的PO,但这个PO在业务领域没有任何显示意义,它完全不能与任何DO对应上。这里需要特别声明,并不是所有多对多关系都没有业务含义,这跟具体业务场景有关,例如:两个PO之间的关系会影响具体业务,并且这种关系存在多种类型,那么这种多对多关系也应该变现为一个DO,又如:”角色“和”资源“之间存在多对多的关系,而这种关系很明显会表现为一个DO–”权限“。
  • 在某些情况下,为了某种持久化策略或者性能的考虑,一个PO可能对应多个DO,反之亦然。例如客户Customer有其联系信息Contacts,这里对应两个一对一的DO,但是可能出于性能的考虑(极端情况),为了减少数据库的连接查询操作,把Customer和Contact两个DO数据合并到一张数据表中。反过来,如果一本图书Book,有一个属性是封面Cover,但该属性是一副图片的二进制数据,而某些数据操作不希望把cover一并加载,那么就需要考虑把cover独立到一张数据表中,这样就形成了一个DO对应一个PO的情况。
  • PO某些属性值对于DO没有任何意义,这些属性值可能是为了解决某些持久化策略而存在的数据,例如为了实现”乐观锁“,PO存在一个version属性,这个version对于DO来说没有任何业务意义,它不应该在DO中存在。同理,DO中也可能存在不需要持久化的属性。

DO和PO的应用

由于ORM框架的功能非常强大而大行其道,而且JavaEE也推出了JPA规范,现在的业务应用开发,基本上不需要区分DO与PO,PO完全可以通过JPA,Hibernate Annotations隐藏在DO之中。虽然如此,但有些问题我们还须注意:

  • 对于DO中不需要持久化的属性,需要通过ORM显式的声明,例如在JPA中,可以利用@Transient声明。
  • 对于PO中为了某种持久化策略而存在的属性,例如version,由于DO,PO合并了,必须在DO中声明,但由于这个属性对DO是没有任何业务意义的,需要让该属性对外隐藏起来,最常见的做法是把该属性的get/set方法私有化,甚至不提供get/set方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/43041.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第2关:创建表

为了完成本关任务,你需要掌握:如何使用HBase shell指令创建表。 Hbase shell操作 启动HBase之后,我们输入hbase shell进入到Hbase shell命令行窗口: hbase shell 在这里我们输入hbase shell会启动一个加入了一些Hbase特有命令的…

list模拟实现(15)

目录 1、简单框架 1、list.h 2、test.cpp 2、list迭代器实现 1、list.h 2、test.cpp 3、思考 1、迭代器中的拷贝构造和赋值重载是否需要自己实现?析构呢? 2、体会类型的力量 3、const迭代器实现 1、list.h 2、test.cpp 4、重载迭代器的oper…

【附源码】计算机毕业设计JAVA知识库系统

【附源码】计算机毕业设计JAVA知识库系统 目运行 环境项配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: JAVA mybatis …

python 使用enumerate()函数详解

一、enumerate() 函数简介 enumerate()是python的内置函数,将一个可遍历iterable数据对象(如list列表、tuple元组或str字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在for循环当中。 函数返回一个enumerate对象,是一…

Metabase学习教程:仪表盘-4

交叉筛选:图表联动更新仪表盘筛选器 只需单击几下,就可以配置任何图表或表以更新仪表盘筛选器。 Metabase允许您自定义当您单击仪表盘部件。本文将详细介绍如何设置部件更新仪表盘上筛选器在我们称之为交叉筛选. 下面是我们要连接的仪表盘&#xff1a…

如何使用轻量应用服务器搭建Veno File Manager个人私有云网盘?

之前有介绍过使用可道云搭建个人云网盘系统:如何使用闲置的云服务器搭建一个属于自己的可道云私人云网盘,这个教程我将介绍使用Veno File Manager搭建个人云网盘,搭建的过程很相似,都采用的是宝塔面板作为辅助,可道云搭…

40 行 Python 代码,写一个 CPU

一、引言 CPU 如何工作?是困扰初级用户一个迷雾般的难题。我们可能知道诸如程序计数器、RAM、寄存器的只言片语,但尚未对这些部件的工作原理及整个系统的协同有清晰和总体的认识。 本文使用四十行 Python 代码来实现一个最简单的 CPU。使它可编程&…

红眼睛微型成像仪拍照、存储与参数复位

拍照在 IFD-x 设备中有两种含义,一是将照片保存于设备内部的存储器(硬拍照),二是将照片 保存于计算机或者手机(软拍照)。 (1)硬拍照 有两种方法来触发一次硬拍照,一…

【树莓派不吃灰】Linux篇⑩ 学习例行性工作排程(核心概念)

目录1. 什么是例行性工作排程2. 仅运行一次的工作排程3. 循环运行的例行性工作排程4. 可唤醒停机期间的工作任务5. 重点回顾❤️ 博客主页 单片机菜鸟哥,一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建记录 2022-11-28 ❤️❤️ 本篇更新记录 2022-11-28 ❤️&…

5、python中的文件操作

文章目录文件操作介绍文件的打开操作open()文件的关闭操作os模块关于操作系统关于路径json模块将python对象编码成json字符串序列化和反序列化常用参数将json字符串解码为python对象存储为excel文件文件操作介绍 文件的作用:数据的持久化存储 一个程序在运行过程中…

小满Vue3第四十六章(Proxy跨域)

1.首先我们先了解一下什么是跨域 主要是出于浏览器的同源策略限制,它是浏览器最核心也最基本的安全功能。 当一个请求url的 协议、域名、端口 三者之间任意一个与当前页面url不同即为跨域。 例如 xxxx.com -> xxxx.com 存在跨域 协议不同 例如 127.x.x.x:800…

Dubbo3.0新特性

服务注册模型 注册模型从接口级别服务注册改为 应用级别服务之策 应用级服务发现简介 概括来说,Dubbo3 引入的应用级服务发现主要有以下优势 适配云原生微服务变革。云原生时代的基础设施能力不断向上释放,像 Kubernetes 等平台都集成了微服务概念抽…

关于各种PLMN的选择

RAT:Radio Access Technology RPLMN:Registered PLMN 终端在上次关机或脱网前登记上的PLMN,会临时保存在USIM卡上 HPLMN: Home PLMN 用户USIM对应IMSI的PLMN EHPLMN:EquivalentHome PLMN,HPLMN对应的运营商可能会有不同的号段,例如中国移动有…

【软考】-- 操作系统(中)

操作系统(中):第三节 存储管理🎀一、存储管理的基本概念1️⃣存储管理2️⃣存储方式分类:3️⃣相对地址4️⃣相对地址空间通过地址再定位机构转换到绝对地址空间(物理地址空间)🎁二、存储方式&a…

WFST--学习笔记

(Weighted Finite-State Transducer):加权有限状态转换机,由有限状态接收机(FSA)拓展而来,在ASR领域常被称为“解码器”。是一个包含了声学模型(H)、上下文相关处理的FST(context-dependency transducer, C…

手画图解 | 关于死锁,面试的一切都在这里了

什么是死锁(Deadlock) 死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。若无外力作用,它们都将无法推进下去。 产生死锁的四个必要条件得烂熟于心: 互斥条件:进程要求对…

艾美捷QuickTiter 逆转录病毒定量试剂盒测定原理

逆转录病毒基因转移是一种有效地将稳定的、可遗传的遗传物质导入大肠杆菌的技术任何分裂细胞类型的基因组。不能复制的逆转录病毒通常通过将逆转录病毒载体转染到包装细胞系中。逆转录病毒根据用于进入宿主细胞的受体。亲嗜性病毒可以识别仅在小鼠身上发现的受体和大鼠细胞。兼…

【信息融合】基于matlab BP神经网络和DS证据理论不确定性信息融合问题【含Matlab源码 2204期】

⛄一、 D-S证据理论及解释 证据理论由Dempster在1967年最初提出,并由他的学生Shafer改进推广使之成为符合有限离散领域中推理的形式,因此称为D-S理论。证据理论讨论一个“辨识框架”(Frame of Discernment)Θ,它是关于命题的相互独立的可能答案或假设的一个有限集合。按传统方…

Qml中的那些坑(三)---MouseArea上的ListView滚轮事件穿透

【写在前面】 最近在 Qml 中使用 MouseArea 时发现了一个奇怪的现象: 位于 MouseArea 上的 ListView 在处理了滚轮事件的情况下进行滚轮,下面的 MouseArea 却在某些情况下接收到了这个事件。 按照直觉,ListView 明明有内部的滚轮事件处理&…

Cesium中的DataSource和Entity关系

本章主要探讨一下Cesium中的DataSource和Entity。 介绍 首先简单说一下Entity与Primitive。 Cesium为开发者提供了丰富的图形绘制和空间数据管理的API,可以分为两类,一类是面向图形开发人员的低层次API,通常被称为Primitive API&#xff0…