当前位置: 首页 > news >正文

DTO和VO的区别及使用场景详解

随着互联网的发展,前后端分离的开发模式越来越流行。在前后端数据交互过程中,为了保证数据的安全性和效率,通常会采用 DTO 和 VO 来封装数据。本篇博客将详细介绍 DTO 和 VO 的区别以及使用场景。 

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

用一个例子来说明可能会比较容易理解:例如服务层有一个getUser的方法返回一个系统用户,其中有一个属性是gender(性别),对于服务层来说,它只从语义上定义:1-男性,2-女性,0-未指定,而对于展示层来说,它可能需要用“帅哥”代表男性,用“美女”代表女性,用“秘密”代表未指定。说到这里,可能你还会反驳,在服务层直接就返回“帅哥美女”不就行了吗?

对于大部分应用来说,这不是问题,但设想一下,如果需求允许客户可以定制风格,而不同风格对于“性别”的表现方式不一样,又或者这个服务同时供多个客户端使用(不同门户),而不同的客户端对于表现层的要求有所不同,那么,问题就来了。再者,回到设计层面上分析,从职责单一原则来看,服务层只负责业务,与具体的表现形式无关,因此,它返回的DTO,不应该出现与表现形式的耦合。

一、什么是VO?什么是DTO?

DTO(Data Transfer Object)和 VO(Value Object)都是一种设计模式,用于封装数据和提供服务。它们的主要区别在于:

  • VO(View Object):视图对象,专门用于前端展示层,专注于表示某个具体的值或对象的对象,包含业务逻辑;VO的作用是将一组数据以适合特定用户界面(UI)的形式封装起来,确保数据的呈现既符合设计要求也满足用户体验标准。
    • 例如我们有一个电子商务网站,其中产品详情页需要显示产品的名称、价格、库存、图片等信息。VO会将这些信息以最优化的方式组织起来,供前端展示。比如,前端可能需要将产品分类显示为“热销商品”、“新品推荐”或“特价优惠”,VO会根据不同的展示需求,以适当的形式提供数据。例如,将从后端接收到的“男性”标签在客户端1上显示为“帅哥”,而在客户端2上显示为“靓仔”。
  • DTO(Data Transfer Object):数据传输对象,侧重于传输数据的对象,不包含业务逻辑;主要在展示层与服务层之间充当媒介,负责数据的标准化传输,确保数据在不同系统或组件间的准确无误传递。
    • 例如当展示层需要向服务层请求数据时,例如查询“男性”类别的产品,它会将“男性”这一概念封装进DTO中,以标准化格式发送请求。服务层接收到这个DTO后,理解为需要查询“男性”类别相关的产品数据,处理后同样以DTO的形式返回,其中可能包括所有“男性”类别的产品信息。这样,无论前端如何展示(如“帅哥”或“靓仔”),后端只需处理统一的“男性”类别,实现了前后端的解耦。

两者的关系:

  • 当DTO与VO一一对应时,DTO等于视图模型,与VO属性值一致
  • 当一个DTO需适配多个VO时,DTO不等于视图模型,与VO不等价

二、使用场景

DTO

✨优点:可以避免数据的重复查询和传输,提高程序的性能和效率。减少代码的复杂度和维护难度,方便代码的开发和维护。因为DTO可以封装业务逻辑和数据格式,减少数据处理的复杂度。,增加数据的安全性。
✨缺点:可能增加开发初期的工作量,尤其是在需要创建和维护多个DTO的情况下,同时过度使用可能引入不必要的复杂性。

VO

✨优点:精确适配前端展示需求,促进前后端解耦,提高代码质量和可读性,因为VO专注于展示层面,避免了业务逻辑的混杂。
✨缺点:在处理大量数据或高并发场景下可能会影响前端性能,而且在多团队协作环境下,对VO的管理和同步可能变得较为复杂。

  • 跨系统数据标准化传输:选DTO,确保数据的一致性和格式标准化,减少数据处理的复杂度。
  • 前后端数据交换与解耦:用DTO,它能匹配前端需求,促进数据交换。
  • 保护敏感信息:DTO可以在数据传输过程中过滤敏感信息,而VO可以在前端展示层剔除非必要信息。
  • 前端展示优化:VO最适合,它针对显示优化,让页面加载更快更准确。
  • 简化代码:DTO和VO都能让代码更模块化,易于理解和维护。

三、在Java中使用DTO和VO的示例代码

1、定义一个基础的实体类,该类中包含用户的一些基本信息:

public class User {private Long id;private String username;private String password;private String email;private String phone;private boolean active;// 构造器,getters 和 setters 省略...
}

2、创建一个DTO,用于在不同层之间传输用户数据,同时隐藏敏感信息,比如密码:

public class UserDTO {private Long id;private String username;private String email;private String phone;private boolean active;public UserDTO(User user) {this.id = user.getId();this.username = user.getUsername();this.email = user.getEmail();this.phone = user.getPhone();this.active = user.isActive();}// getters 和 setters 省略...
}

3、定义一个VO,专门用于前端展示,进一步精简信息,只包含前端需要显示的数据:
 

public class UserVO {private String username;private String contactInfo;public UserVO(UserDTO userDTO) {this.username = userDTO.getUsername();this.contactInfo = userDTO.getEmail() + " | " + userDTO.getPhone();}// getters 和 setters 省略...
}

UserDTO从User实体类中获取数据,但不包含敏感信息,如密码。UserDTO 类,用于封装从数据库中查询出来的用户数据,并将其转换为前端需要的格式。该类只包含必要的属性(id、username、email),可以避免不必要的数据传输,提高程序的性能和效率。UserVO进一步从UserDTO中提取数据,只保留前端展示所需的用户名和联系方式。

以下是UserDTO和UserVO的转换示例,实际应用中,构造器和方法可能需要更详细的错误检查和边界处理,这里为了简洁起见,省略了这部分细节。通常会使用框架提供的工具或库(如ModelMapper、AutoMapper)来自动完成实体、DTO和VO之间的转换:

// 假设我们有一个User实例
User user = new User();
// ...设置user的属性...// 将User转换为UserDTO
UserDTO userDTO = new UserDTO(user);// 将UserDTO转换为UserVO
UserVO userVO = new UserVO(userDTO);

可以看到,UserDTO 包含了用户信息的全部属性,用于在前后端之间传输数据。由于 DTO 只包含数据属性,不包含任何业务逻辑,因此可以避免数据的重复查询和传输,提高程序的性能和效率。

需要注意的是,DTO 和 VO 只是一种设计模式,具体的实现方式可以根据具体的业务需求和技术架构来选择。在实际开发中,可以根据需要使用 DTO、VO 或者其他方案来封装数据。


参考链接:https://blog.csdn.net/qq_46207024/article/details/134726394

                https://zhuanlan.zhihu.com/p/79968039

相关文章:

DTO和VO的区别及使用场景详解

随着互联网的发展,前后端分离的开发模式越来越流行。在前后端数据交互过程中,为了保证数据的安全性和效率,通常会采用 DTO 和 VO 来封装数据。本篇博客将详细介绍 DTO 和 VO 的区别以及使用场景。 大家可能会有个疑问,既然DTO是展…...

百度在下一盘大棋

这两天世界互联网大会在乌镇又召开了。 我看到一条新闻,今年世界互联网大会乌镇峰会发布“2024 年度中国互联网企业创新发展十大典型案例”,百度文心智能体平台入选。 这个智能体平台我最近也有所关注,接下来我就来讲讲它。 百度在下一盘大棋…...

第十六届蓝桥杯模拟赛第二期题解—Java

第十六届蓝桥杯模拟赛/校赛第二期个人题解,有错误的地方欢迎各位大佬指正 问题一(填空题) 【问题描述】 如果一个数 p 是个质数,同时又是整数 a 的约数,则 p 称为 a 的一个质因数。 请问, 2024 的最大的质因数是多少? …...

驱动开发笔记:关于3588GPIO

1.概要 2.内容 1.3588GPIO 关于RK3588的GPIO(General-Purpose Input/Output,通用输入输出引脚),以下是一些关键信息和操作指南: 一、GPIO基本概念 定义:GPIO是嵌入式系统中常见的通信接口,…...

【RK3588 Linux 5.x 内核编程】-内核线程与Mutex

内核线程与Mutex 文章目录 内核线程与Mutex1、Mutex介绍1.1 竞争条件1.2 Mutex特性2、Linux内核中的Mutex2.1 初始化Mutex2.1.1 静态方式初始化2.1.2 动态方式初始化2.2 互斥锁获取2.3 互斥锁释放3、Mutex使用示例4、驱动验证在前面的文章中,介绍了如何Linux内核中的线程,但是…...

【0342】分配并初始化 Proc Signal 共享内存 (1)

1. Proc Signal (procsignal)共享内存 Postgres内核在启动postmaster守护进程时候, 会通过函数 ProcSignalShmemInit() 去为 Proc Signal 分配并初始化指定大小的共享内存空间。整个调用链路如下。 (gdb) bt #0 ProcSignalShmemInit () at procsignal.c:118 #1 0x000000000…...

管家婆财贸ERP BR035.回款利润明细表

最低适用版本: 财贸系列 23.5 插件简要功能说明: 报表统计销售单/销售退货单/销售发票回款情况更多细节描述见下方详细文档插件操作视频: 进销存类定制插件--回款利润明细表 插件详细功能文档: 1. 应用中心增加报表【回款利润明细表】 a. b. 查询条件: ⅰ. 日期区间:…...

数据库MYSQL——表的设计

文章目录 前言三大范式:几种实体间的关系:一对一关系:一对多关系:多对多关系: 前言 之前的博客中我们讲解的是关于数据库的增删改查与约束的基本操作, 是在已经创建数据库,表之上的操作。 在实…...

netstat -tuln | grep 27017(显示所有监听状态的 TCP 和 UDP 端口,并且以数字形式显示地址和端口号)

文章目录 1. 确定占用端口的进程使用 lsof 命令使用 fuser 命令 2. 结束占用端口的进程3. 修改 MongoDB 配置文件4. 检查 MongoDB 日志文件5. 重新启动 MongoDB 服务6. 检查 MongoDB 服务状态总结 [rootlocalhost etc]# netstat -tuln | grep 27017 tcp 0 0 127.0.…...

非线性控制器设计原理

非线性控制器设计原理 非线性控制器设计旨在解决非线性系统的控制问题,克服传统线性控制器在处理非线性现象(如饱和、死区、耦合、时变性等)时的不足。其核心在于利用非线性数学工具和设计方法,使控制系统在非线性条件下具备良好…...

MySQL数据库6——SQL优化

一.SQL优化 1.插入优化 优化1:批量插入 insert into 表名 values(记录1),(记录2),……;优化2:手动提交事务 start transaction; insert into 表名 values(记录1),(记录2); insert into 表名 values(记录1),(记录2); …… commit;优化3:主键顺…...

IDEA配置本地maven

因为idea和maven是没有直接关系的。所以使用idea创建maven工程之前需要将本地的maven配置到idea环境中,这样才可以在idea中创建maven工程。配置方法如下: 1.1 配置本地maven 第一步:关闭当前工程,回到idea主界面找到customize--…...

学习日记_20241123_聚类方法(高斯混合模型)续

前言 提醒: 文章内容为方便作者自己后日复习与查阅而进行的书写与发布,其中引用内容都会使用链接表明出处(如有侵权问题,请及时联系)。 其中内容多为一次书写,缺少检查与订正,如有问题或其他拓展…...

SpringMVC——简介及入门

SpringMVC简介 看到SpringMVC这个名字,我们会发现其中包含Spring,那么SpringMVC和Spring之间有怎样的关系呢? SpringMVC隶属于Spring,是Spring技术中的一部分。 那么SpringMVC是用来做什么的呢? 回想web阶段&#x…...

文件操作完成后,为什么要关闭文件

原因包括: 释放系统资源:打开文件时,操作系统会分配资源,如文件描述符或句柄,用于管理文件访问。如果文件保持打开状态,这些资源就不会被释放,可能导致资源耗尽。 确保数据完整性:写…...

vue3+echarts+ant design vue实现进度环形图

1、代码 <div> <!-- 目标环形图 --><div id"main" class"chart_box"> </div><div class"text_target">目标</div> </div>// 目标环形图 const onEcharts () > {// 基于准备好的dom&#xff0c;初…...

使用argo workflow 实现springboot 项目的CI、CD

文章目录 基础镜像制作基础镜像设置镜像源并安装工具git下载和安装 Maven设置环境变量设置工作目录默认命令最终dockerfile 制作ci argo workflow 模版volumeClaimTemplatestemplatesvolumes完整workflow文件 制作cd argo workflow 模版Workflow 结构Templates 定义创建 Kubern…...

C++知识点总结(58):序列型动态规划

动态规划Ⅰ 一、基础1. 意义2. 序列 dp 解法 二、例题1. 最大子段和2. 删数最大子段和&#xff08;数据强度&#xff1a;pro max&#xff09;3. 最长上升子序列&#xff08;数据强度&#xff1a;pro max&#xff09;4. 3 或 5 的倍数序列5. 数码约数序列 一、基础 1. 意义 动…...

go interface(接口)使用

在 Go 语言中&#xff0c;接口&#xff08;interface&#xff09;是一种抽象类型&#xff0c;它定义了一组方法&#xff0c;但是不实现这些方法。接口指定了一个对象的行为&#xff0c;而不关心对象的具体实现。接口使得代码更加灵活和可扩展。 定义接口 接口使用 type 关键字…...

【docker】docker commit 命令 将当前容器的状态保存为一个新的镜像

在Docker容器中安装了许多软件&#xff0c;并希望将当前容器的状态保存为一个新的镜像&#xff0c;可以使用docker commit命令来创建一个新的镜像。以下是如何操作的步骤&#xff1a; 找到容器ID或名称&#xff1a; 首先&#xff0c;需要找到想要保存的容器的ID或名称。可以使用…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...

快刀集(1): 一刀斩断视频片头广告

一刀流&#xff1a;用一个简单脚本&#xff0c;秒杀视频片头广告&#xff0c;还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农&#xff0c;平时写代码之余看看电影、补补片&#xff0c;是再正常不过的事。 电影嘛&#xff0c;要沉浸&#xff0c;…...