告别编程概念混乱!VO、BO、PO、DTO、DO 一篇全懂

3 阅读24分钟

告别编程概念混乱!VO、BO、PO、DTO、DO 一篇全懂

开头引入

宝子们,作为一个在编程世界摸爬滚打的博主,我深知大家在学习编程概念时的那种迷茫与困惑。就拿我来说吧,曾经被 VO、BO、PO、DTO、DO 这些看似相似却又有着微妙区别的概念搞得晕头转向,代码写着写着就开始怀疑人生,感觉自己像是在一团迷雾中徘徊,怎么也找不到出口。我猜不少小伙伴也和我有过同样的经历,每次遇到这些概念,心里就开始打鼓:它们到底有啥区别啊?什么时候该用哪个呢?别担心,今天这篇文章就是来给大家拨云见日的,一次性把 VO、BO、PO、DTO、DO 讲透,让你不再为这些概念而烦恼 !

它们是什么?

在正式开讲之前,咱们先来看看这几个概念的 “真面目”:

  • VO(View Object):视图对象,用于展示层,它的作用是把业务数据按照前端展示的需求进行封装,让数据以友好的方式呈现给用户 。比如说,前端页面需要展示用户的订单信息,VO 就会把订单的相关数据(如订单号、商品名称、价格等)整理好,传递给前端。

  • BO(Business Object):业务对象,主要用于业务逻辑层,它封装了业务逻辑和业务规则,将业务操作封装成方法,供上层应用调用 。比如用户下单购买商品,BO 会处理诸如库存检查、价格计算、订单生成等一系列业务逻辑。

  • PO(Persistent Object):持久化对象,和数据库表结构一一对应,主要用于数据的持久化操作,也就是和数据库打交道,进行数据的增删改查 。比如将用户的订单信息保存到数据库中,就会用到 PO。

  • DTO(Data Transfer Object):数据传输对象,用于不同层之间的数据传输,它可以减少数据传输量,提高传输效率,同时也能隔离不同层之间的依赖 。比如在前后端交互中,DTO 可以把前端需要的数据封装好传递给前端,或者把前端传来的数据封装好传递给后端的业务层。

  • DO(Domain Object):领域对象,是对现实世界中业务实体的抽象,它包含了业务数据和业务行为,是领域模型的核心 。比如用户、商品、订单等都可以抽象成 DO,它们有自己的属性(如用户的姓名、年龄,商品的名称、价格等)和行为(如用户下单、商品打折等)。

逐个击破:PO(持久化对象)

(一)含义

PO 即 Persistent Object,持久化对象,它就像是数据库表的 “贴身翻译”,与数据库表结构直接映射 。PO 中的每一个字段都和数据库表的列完全一致,就好比数据库表是一座房子,PO 就是这座房子的精确蓝图,每一个细节都对应得严丝合缝。当我们从数据库中读取数据时,数据就会被填充到 PO 对象中,成为我们在程序中可以操作的数据实体;而当我们要将数据保存到数据库时,PO 对象又会将数据准确无误地传递给数据库。 简单来说,PO 是数据库记录在 Java 程序中的直接体现,它的存在就是为了方便我们与数据库进行交互,让数据的持久化操作变得更加简单和直观。

(二)使用场景

PO 的使用场景相对比较单一,主要活跃在数据访问层 。在使用 MyBatis 进行数据库操作时,我们在 Mapper 接口中定义的方法参数或者返回值常常就是 PO 对象。在 XML 映射文件中,也会通过配置将数据库查询结果映射到 PO 对象上 。比如,我们要查询用户表中的所有用户信息,Mapper 接口中的方法可能是这样定义的:List<UserPO> selectAllUsers();,这里的 UserPO 就是与用户表对应的 PO 类。又比如要插入一条用户数据,方法可能是void insertUser(UserPO userPO);,通过传入 UserPO 对象,将用户数据插入到数据库中。总之,PO 就像是数据访问层与数据库之间的桥梁,负责数据的传递和接收 。

(三)实操示例

下面我们来看一个简单的 PO 类代码示例,假设我们有一个用户表 user,包含 id、name、phone、password、role_id、create_time 这些字段,对应的 UserPO 类代码如下:


public class UserPO {
    // 对应数据库user表的id列
    private Long id;
    // 对应数据库user表的name列
    private String name;
    // 对应数据库user表的phone列
    private String phone;
    // 对应数据库user表的password列(加密存储)
    private String password;
    // 对应数据库user表的role_id列
    private Long roleId;
    // 对应数据库user表的create_time列
    private LocalDateTime createTime;

    // 提供getter和setter方法
    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Long getRoleId() {
        return roleId;
    }

    public void setRoleId(Long roleId) {
        this.roleId = roleId;
    }

    public LocalDateTime getCreateTime() {
        return createTime;
    }

    public void setCreateTime(LocalDateTime createTime) {
        this.createTime = createTime;
    }
}

在这个示例中,UserPO 类的字段与数据库 user 表的列一一对应,并且只提供了基本的 getter 和 setter 方法,没有任何业务逻辑,完全符合 PO 的定义和使用规范 。

逐个击破:DO(领域对象)

(一)含义

DO,即 Domain Object,领域对象,是领域驱动设计(DDD)中的核心概念 。它是对现实世界中业务实体的一种抽象表达,就像是把现实中的事物搬进了代码世界,不仅包含了业务数据,还封装了相关的业务行为 。比如说在一个电商系统中,用户下单购买商品这一行为,涉及到用户信息、商品信息、订单信息等数据,以及库存检查、价格计算、订单生成等业务逻辑,这些都可以封装在一个订单 DO 中 。与 PO 那种 “贫血模型”(只有数据,没有业务逻辑)不同,DO 是 “充血模型”,它将数据和行为紧密结合,让业务逻辑更加清晰和集中 。这就好比一个人,PO 只是记录了这个人的外貌特征(数据),而 DO 不仅有外貌特征,还具备了思考、行动等能力(业务逻辑和行为) 。

(二)使用场景

DO 主要在核心业务逻辑层大显身手 。当我们进行复杂业务逻辑处理时,DO 就会派上用场 。在订单业务中,订单 DO 不仅包含订单的编号、下单时间、订单金额等基本信息,还包含了计算订单总价、判断订单状态是否可修改、处理订单支付等业务方法 。通过这些方法,订单 DO 可以自主完成一系列与订单相关的业务操作,而不需要在业务逻辑层中散落大量的业务逻辑代码 。又比如在一个社交平台中,用户 DO 可以包含用户的注册、登录、关注、取消关注等业务行为,以及用户的昵称、头像、性别等基本信息 。这样,在处理用户相关业务时,我们可以直接调用用户 DO 的业务方法,让代码更加简洁和易维护 。

(三)实操示例

下面我们来看一个 UserDO 的代码示例,假设我们在开发一个用户管理系统,UserDO 类可能如下:


public class UserDO {
    // 用户id
    private Long id;
    // 用户名
    private String username;
    // 密码
    private String password;
    // 邮箱
    private String email;

    // 注册方法
    public void register(String username, String password, String email) {
        this.username = username;
        this.password = password;
        this.email = email;
        // 这里可以添加一些注册的业务逻辑,比如生成唯一的用户id,发送注册成功邮件等
    }

    // 登录方法
    public boolean login(String username, String password) {
        return this.username.equals(username) && this.password.equals(password);
    }

    // 获取用户信息方法
    public String getUserInfo() {
        return "用户名:" + username + ",邮箱:" + email;
    }

    // 省略getter和setter方法
}

在这个示例中,UserDO 类不仅包含了用户的基本信息(数据),还包含了注册、登录、获取用户信息等业务方法(业务逻辑和行为) 。通过这些方法,UserDO 可以完成与用户相关的核心业务操作,体现了 DO 的 “充血模型” 特点 。

逐个击破:BO(业务对象)

(一)含义

BO,也就是 Business Object,业务对象,是由多个 DO 或 PO 组合而成的复合对象 。它是从业务角度出发,将相关的业务数据和业务逻辑封装在一起,形成一个独立的业务单元,用于完成特定的业务场景 。比如说在一个电商系统中,订单业务对象(OrderBO)可能会包含订单 DO、用户 DO、订单项 DO 等,同时还会包含计算订单总价、判断订单是否有效、处理订单支付等业务方法 。BO 就像是一个业务专家,它了解业务的规则和流程,能够根据不同的业务场景进行相应的业务操作,为上层应用提供高效、便捷的业务服务 。

(二)使用场景

BO 主要活跃在业务逻辑层,当我们遇到复杂的业务逻辑处理,需要多个实体协作才能完成业务功能时,BO 就派上用场了 。在订单业务中,一个订单不仅仅是简单的订单信息,还涉及到用户信息、商品信息、支付信息等多个方面 。此时,我们可以创建一个 OrderBO,将订单、用户、订单项等相关的 DO 或 PO 组合在一起,通过 OrderBO 的业务方法来处理订单相关的业务逻辑,如计算订单总价、检查库存、处理优惠活动等 。又比如在一个社交平台中,用户关注、点赞、评论等业务操作,都可以封装在相应的 BO 中,通过 BO 来管理和处理这些业务逻辑,让业务代码更加集中和易于维护 。

(三)实操示例

下面我们以一个电商系统中的订单业务为例,来看一下 OrderBO 的代码示例:


import java.math.BigDecimal;
import java.util.List;

public class OrderBO {
    // 订单DO
    private OrderDO orderDO;
    // 用户DO
    private UserDO userDO;
    // 订单项DO列表
    private List<OrderItemDO> orderItemDOList;

    // 计算订单总价的方法
    public BigDecimal calculateTotalPrice() {
        BigDecimal totalPrice = BigDecimal.ZERO;
        for (OrderItemDO orderItemDO : orderItemDOList) {
            totalPrice = totalPrice.add(orderItemDO.getPrice().multiply(new BigDecimal(orderItemDO.getQuantity())));
        }
        return totalPrice;
    }

    // 判断订单是否有效(这里简单示例,实际可能更复杂)
    public boolean isOrderValid() {
        return orderDO.getStatus() == OrderStatusEnum.VALID.getCode();
    }

    // 处理订单支付的方法(这里简单示例,实际可能涉及第三方支付接口调用等)
    public void processPayment() {
        // 模拟支付成功,修改订单状态
        orderDO.setStatus(OrderStatusEnum.PAID.getCode());
    }

    // 省略getter和setter方法
}

在这个示例中,OrderBO 组合了 OrderDO、UserDO 和 OrderItemDOList,通过 calculateTotalPrice 方法计算订单总价,isOrderValid 方法判断订单是否有效,processPayment 方法处理订单支付,体现了 BO 在业务逻辑处理中的核心作用 。

逐个击破:DTO(数据传输对象)

(一)含义

DTO,也就是 Data Transfer Object,数据传输对象,主要用于进程间的数据传输 。在一个 Java Web 项目中,当我们从数据库中查询数据后,要将数据从 Service 层传递到 Controller 层,再返回给前端展示,这个过程中就可能会用到 DTO 。它就像是一个数据快递员,把数据从一个地方安全、高效地送到另一个地方 。DTO 通常是一个简单的 Java 类,只包含字段和对应的 getter 和 setter 方法,没有复杂的业务逻辑,就像一个干净整洁的包裹,只负责装数据,不负责处理其他事情 。并且,它是一种扁平化的数据结构,字段不需要与数据库表结构一一对应,完全是根据数据传输的需求来定义的 。

(二)使用场景

在不同层之间传递数据时,DTO 就派上用场了 。当我们从数据库中查询出用户的详细信息(包括密码等敏感信息),但在返回给前端展示时,我们不希望把密码也返回过去,这时就可以使用 DTO,只把前端需要的用户基本信息(如用户名、邮箱等)封装到 DTO 中传递给前端 。又比如在微服务架构中,不同微服务之间进行通信时,也常常使用 DTO 来传递数据,这样可以减少数据传输量,提高传输效率,同时还能隔离不同微服务之间的依赖 。总的来说,DTO 就像是一座桥梁,连接着不同的层或服务,让数据在它们之间顺畅地流动 。

(三)实操示例

假设我们有一个用户管理系统,在 Service 层查询出用户信息后,需要返回给 Controller 层,再返回给前端展示 。我们可以定义一个 UserDTO 类来封装需要返回的数据,代码如下:


public class UserDTO {
    // 用户id
    private Long id;
    // 用户名
    private String username;
    // 邮箱
    private String email;

    // 省略getter和setter方法
}

在 Service 层中,我们可以将查询到的用户信息(可能是一个 UserDO 或 UserPO 对象)转换为 UserDTO 对象,然后返回给 Controller 层 。例如:


@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDTO getUserById(Long id) {
        UserDO userDO = userMapper.selectUserById(id);
        UserDTO userDTO = new UserDTO();
        userDTO.setId(userDO.getId());
        userDTO.setUsername(userDO.getUsername());
        userDTO.setEmail(userDO.getEmail());
        return userDTO;
    }
}

在 Controller 层中,我们接收 Service 层返回的 UserDTO 对象,并返回给前端:


@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public UserDTO getUserById(@PathVariable Long id) {
        return userService.getUserById(id);
    }
}

通过这样的方式,我们使用 UserDTO 在 Service 层和 Controller 层之间传递数据,确保了数据的安全性和传输效率 。

逐个击破:VO(视图对象)

(一)含义

VO,即 View Object,视图对象,是专门为前端展示而设计的 。它就像是一个贴心的设计师,根据前端页面的需求,精心挑选和整理数据,把最适合展示的数据呈现给用户 。VO 中只包含前端需要展示的数据,不包含任何业务逻辑,它的任务就是把数据 “打扮” 得漂漂亮亮的,让前端页面能够轻松地展示这些数据 。比如说,前端需要展示用户的订单列表,VO 就会把订单的编号、下单时间、商品名称、价格等信息整理好,传递给前端,而不会把订单的一些内部处理状态等不需要展示的数据也传过去 。

(二)使用场景

VO 主要在控制层(Controller)与前端进行交互时使用 。当后端处理完业务逻辑,需要将数据返回给前端展示时,就会把数据封装成 VO 对象 。在一个电商系统中,用户查看订单详情,后端从数据库中查询出订单的详细信息(可能是一个 OrderDO 对象),然后将订单号、商品信息、收货地址、支付状态等前端需要展示的数据提取出来,封装成 OrderVO 对象,返回给前端 。这样做的好处是,既保证了数据展示的友好性,又避免了将一些敏感信息(如订单的内部处理细节、数据库字段等)暴露给前端 。

(三)实操示例

假设我们有一个电商系统,现在要展示订单信息给用户 。我们定义一个 OrderVO 类,代码如下:


public class OrderVO {
    // 订单编号
    private String orderId;
    // 下单时间,格式化为"yyyy-MM-dd HH:mm:ss"
    private String orderTime;
    // 商品名称
    private String productName;
    // 商品数量
    private Integer quantity;
    // 订单总价
    private BigDecimal totalPrice;
    // 支付状态,"已支付"、"未支付"、"支付失败"等
    private String paymentStatus;

    // 省略getter和setter方法
}

在 Controller 层中,我们可以这样将订单数据封装成 OrderVO 并返回给前端:


@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private OrderService orderService;

    @GetMapping("/{orderId}")
    public OrderVO getOrderById(@PathVariable String orderId) {
        OrderDO orderDO = orderService.getOrderById(orderId);
        OrderVO orderVO = new OrderVO();
        orderVO.setOrderId(orderDO.getOrderId());
        // 格式化下单时间
        orderVO.setOrderTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(orderDO.getOrderTime()));
        orderVO.setProductName(orderDO.getProductName());
        orderVO.setQuantity(orderDO.getQuantity());
        orderVO.setTotalPrice(orderDO.getTotalPrice());
        // 将支付状态码转换为文字描述
        if (orderDO.getPaymentStatus() == 1) {
            orderVO.setPaymentStatus("已支付");
        } else if (orderDO.getPaymentStatus() == 2) {
            orderVO.setPaymentStatus("未支付");
        } else {
            orderVO.setPaymentStatus("支付失败");
        }
        return orderVO;
    }
}

在这个示例中,OrderVO 根据前端的展示需求,对订单数据进行了处理和封装,如格式化下单时间、将支付状态码转换为文字描述等,使得前端能够方便地展示订单信息 。

实际应用中的流转

(一)下单场景举例

为了让大家更直观地理解 VO、BO、PO、DTO、DO 在实际业务中的协同工作和数据流转,我们以用户下单买奶茶为例,来详细剖析一下整个过程 。

假设用户在奶茶店的 App 上下单购买一杯芋泥波波奶茶,选择大杯、少冰、五分糖 。用户点击 “提交订单” 按钮后,前端会将订单相关信息(如用户 ID、商品 ID、规格等)封装成一个 CreateOrderVO 对象,然后通过网络请求发送给后端 。这个 CreateOrderVO 就像是一个装满订单信息的 “包裹”,专门为前端展示和传输而准备 。

后端的 Controller 层接收到这个请求后,会将 CreateOrderVO 中的数据提取出来,封装成一个 CreateOrderDTO 对象,传递给 Service 层 。这里的 CreateOrderDTO 就像是一个 “中转包裹”,它只包含了 Service 层处理订单业务所需要的数据,去除了一些前端展示相关但业务处理不需要的信息 。

Service 层接收到 CreateOrderDTO 后,会根据其中的信息构建 OrderDO 对象 。OrderDO 是订单的领域对象,它包含了订单的核心业务数据和业务逻辑 。在构建 OrderDO 的过程中,可能会涉及到从其他地方获取相关数据,比如根据商品 ID 获取商品的详细信息(如商品名称、原价等),根据用户 ID 获取用户的会员信息(用于计算会员折扣)等 。构建好 OrderDO 后,会调用 OrderDO 的业务方法进行一些业务逻辑处理,比如计算最终价格(考虑会员折扣、商品促销等因素)、检查库存是否足够等 。

如果业务逻辑处理都通过,Service 层会将 OrderDO 进一步封装成 OrderBO 对象 。OrderBO 是业务对象,它将多个相关的 DO 组合在一起,完成特定的业务场景 。在这个例子中,OrderBO 可能会包含 OrderDO、UserDO(用户信息)、MilkTeaDO(奶茶商品信息)等,并且会包含一些业务方法,如处理订单支付、更新订单状态等 。

接下来,OrderBO 会调用相关的服务进行后续操作,比如调用库存服务检查库存并扣减库存,调用支付服务处理订单支付等 。在处理完所有业务逻辑后,需要将订单信息保存到数据库中 。这时,会将 OrderBO 中的数据转换为 OrderPO 对象 。OrderPO 是持久化对象,与数据库表结构一一对应,它的作用就是将订单数据准确无误地保存到数据库中 。

数据库操作完成后,会返回一个成功的响应 。Service 层会将这个响应封装成一个 OrderDTO 对象,返回给 Controller 层 。这个 OrderDTO 只包含了前端需要展示的订单相关信息,如订单编号、实付金额、订单状态等 。

最后,Controller 层接收到 OrderDTO 后,会将其转换为 OrderVO 对象,返回给前端 。OrderVO 是专门为前端展示而设计的,它会对数据进行一些格式化处理,比如将订单时间格式化为 “yyyy-MM-dd HH:mm:ss” 的形式,将订单状态码转换为文字描述(如 “已支付”、“未支付” 等),让数据以友好的方式呈现给用户 。用户在 App 上看到 “订单创建成功” 的页面,并能查看到订单的详细信息 。

(二)结合代码说明

下面我们给出每个环节对应的代码片段,帮助大家更好地理解它们在实际业务中的使用和相互关系 。

  1. 前端请求:前端将订单信息封装成 CreateOrderVO 对象,并发送请求 。(这里用 JavaScript 示例前端代码,假设使用 Axios 库发送请求)

import axios from 'axios';

const createOrderVO = {
    userId: 123,
    productId: 456,
    spec: {
        size: '大杯',
        ice: '少冰',
        sugar: '五分糖'
    }
};

axios.post('/order/create', createOrderVO)
  .then(response => {
        console.log('订单创建成功', response.data);
    })
  .catch(error => {
        console.error('订单创建失败', error);
    });
  1. Controller 层:接收前端请求,将 CreateOrderVO 转换为 CreateOrderDTO,并调用 Service 层方法 。

@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private OrderService orderService;

    @PostMapping("/create")
    public OrderVO createOrder(@RequestBody CreateOrderVO createOrderVO) {
        CreateOrderDTO createOrderDTO = new CreateOrderDTO();
        createOrderDTO.setUserId(createOrderVO.getUserId());
        createOrderDTO.setProductId(createOrderVO.getProductId());
        createOrderDTO.setSpec(createOrderVO.getSpec());

        OrderDTO orderDTO = orderService.createOrder(createOrderDTO);

        OrderVO orderVO = new OrderVO();
        orderVO.setOrderId(orderDTO.getOrderId());
        orderVO.setPayAmount(orderDTO.getPayAmount());
        orderVO.setStatusDesc(orderDTO.getStatusDesc());
        orderVO.setCreateTime(orderDTO.getCreateTime());

        return orderVO;
    }
}
  1. Service 层:接收 CreateOrderDTO,构建 OrderDO,进行业务逻辑处理,封装成 OrderBO,调用相关服务,将结果转换为 OrderDTO 返回 。

@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private PaymentService paymentService;

    public OrderDTO createOrder(CreateOrderDTO createOrderDTO) {
        // 构建OrderDO
        OrderDO orderDO = new OrderDO();
        orderDO.setUserId(createOrderDTO.getUserId());
        orderDO.setProductId(createOrderDTO.getProductId());
        orderDO.setSpec(createOrderDTO.getSpec());

        // 调用OrderDO的业务方法进行业务逻辑处理
        orderDO.checkBeforeCreate();
        Money payAmount = orderDO.calcFinalPrice();

        // 封装成OrderBO
        OrderBO orderBO = new OrderBO();
        orderBO.setOrderDO(orderDO);
        orderBO.setUserDO(getUserDO(orderDO.getUserId()));
        orderBO.setMilkTeaDO(getMilkTeaDO(orderDO.getProductId()));

        // 调用库存服务扣减库存
        inventoryService.lock(orderBO.getOrderDO().getSpec());

        // 调用支付服务处理支付(这里简单示例,实际可能更复杂)
        paymentService.processPayment(orderBO);

        // 将OrderBO转换为OrderPO并保存到数据库
        OrderPO orderPO = new OrderPO();
        orderPO.setUserId(orderBO.getOrderDO().getUserId());
        orderPO.setProductId(orderBO.getOrderDO().getProductId());
        orderPO.setSku(JSON.toJSONString(orderBO.getOrderDO().getSpec()));
        orderPO.setPrice(orderBO.getMilkTeaDO().getPrice());
        orderPO.setPayAmount(payAmount.getValue());
        orderPO.setStatus(OrderStatusEnum.CREATED.getCode());
        orderPO.setCreateTime(LocalDateTime.now());
        orderPO.setUpdateTime(LocalDateTime.now());

        orderRepository.save(orderPO);

        // 将结果转换为OrderDTO返回
        OrderDTO orderDTO = new OrderDTO();
        orderDTO.setOrderId(orderPO.getId());
        orderDTO.setProductName(orderBO.getMilkTeaDO().getName());
        orderDTO.setPayAmount(payAmount.getValue());
        orderDTO.setStatusDesc(OrderStatusEnum.CREATED.getDesc());
        orderDTO.setCreateTime(orderPO.getCreateTime());

        return orderDTO;
    }

    private UserDO getUserDO(Long userId) {
        // 这里省略从数据库或其他地方获取用户信息的逻辑
        return new UserDO();
    }

    private MilkTeaDO getMilkTeaDO(Long productId) {
        // 这里省略从数据库或其他地方获取奶茶商品信息的逻辑
        return new MilkTeaDO();
    }
}
  1. OrderDO:订单领域对象,包含业务数据和业务方法 。

public class OrderDO {
    private Long id;
    private Long userId;
    private Long productId;
    private SpecDO spec;
    private Money price;
    private OrderStatus status;

    // 业务方法:计算最终价格
    public Money calcFinalPrice() {
        // 会员折扣
        UserDO userDO = getUserDO(userId);
        Money discount = userDO.getVipDiscount();
        // 商品促销
        MilkTeaDO milkTeaDO = getMilkTeaDO(productId);
        Money promotion = milkTeaDO.getPromotion(spec);
        return price.minus(discount).minus(promotion);
    }

    // 业务方法:下单前置校验
    public void checkBeforeCreate() {
        MilkTeaDO milkTeaDO = getMilkTeaDO(productId);
        if (!milkTeaDO.hasStock(spec)) {
            throw new BizException("库存不足");
        }
    }

    private UserDO getUserDO(Long userId) {
        // 这里省略从数据库或其他地方获取用户信息的逻辑
        return new UserDO();
    }

    private MilkTeaDO getMilkTeaDO(Long productId) {
        // 这里省略从数据库或其他地方获取奶茶商品信息的逻辑
        return new MilkTeaDO();
    }
}
  1. OrderBO:订单业务对象,组合多个 DO,包含业务逻辑方法 。

public class OrderBO {
    private OrderDO orderDO;
    private UserDO userDO;
    private MilkTeaDO milkTeaDO;

    // 处理订单支付的方法(这里简单示例,实际可能涉及第三方支付接口调用等)
    public void processPayment() {
        // 模拟支付成功,修改订单状态
        orderDO.setStatus(OrderStatusEnum.PAID);
    }
}
  1. OrderPO:订单持久化对象,与数据库表结构一一对应 。

@Data
@TableName("t_order")
public class OrderPO {
    private Long id;
    private Long userId;
    private Long productId;
    private String sku;
    private BigDecimal price;
    private BigDecimal payAmount;
    private Integer status;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}
  1. CreateOrderDTO:前端到后端的入口 DTO 。

@Data
public class CreateOrderDTO {
    @NotNull
    private Long userId;
    @NotNull
    private Long productId;
    @Valid
    private SpecDTO spec;
}
  1. OrderDTO:后端到前端的出口 DTO 。

@Data
public class OrderDTO {
    private Long orderId;
    private String productName;
    private BigDecimal payAmount;
    private String statusDesc;
    private LocalDateTime createTime;
}
  1. OrderVO:前端展示的视图对象 。

@Data
public class OrderVO {
    private String orderId;
    private String productName;
    private String priceText;
    private String statusTag;
    private String createTime;
}

通过以上代码示例和详细解释,相信大家对 VO、BO、PO、DTO、DO 在实际业务中的协同工作和数据流转有了更清晰的认识 。在实际开发中,合理地使用这些对象,可以使代码结构更加清晰,易于维护和扩展 。

常见问题解答

(一)为什么不合并某些对象

在实际开发中,有些小伙伴可能会有疑问,为什么不把 DO 和 PO 合并呢?为什么 DTO 和 VO 不能合并呢?其实,这些对象的存在都有其必要性,它们各司其职,共同保障系统的稳定运行 。

先来说说 DO 和 PO,DO 是领域对象,它封装了业务行为和业务逻辑,而 PO 是持久化对象,主要负责与数据库表结构的映射 。如果将它们合并,当数据库结构发生变化时,比如添加一个字段或者修改字段类型,就可能会影响到业务逻辑代码 。而业务规则发生改变时,又可能会影响到数据库相关的代码 。这样就会导致代码的耦合度增加,维护起来非常困难 。通过将 DO 和 PO 分离,可以有效地隔离变化,让业务逻辑和数据持久化逻辑相互独立,提高代码的可维护性和可扩展性 。

再看看 DTO 和 VO,虽然在一些小项目中,它们的功能可能有部分重叠,合并起来似乎也能正常工作 。但是一旦项目规模扩大,尤其是涉及到微服务架构或者多端应用(如 App、小程序、管理后台)时,合并 DTO 和 VO 就会带来很多问题 。不同的客户端对数据的需求和展示方式可能不同,如果将 DTO 和 VO 合并,就很难满足这些多样化的需求 。比如,管理后台可能需要展示用户的详细信息,包括手机号等敏感信息,而 App 可能只需要展示用户的基本信息,不需要手机号 。如果合并了 DTO 和 VO,就可能会导致前端获取到一些不该看到的数据,增加了数据泄露的风险 。此外,在微服务架构中,不同微服务之间的数据传输对数据结构的要求也比较严格,合并 DTO 和 VO 可能会导致数据传输出现问题 。所以,为了适应不同的业务场景和数据传输需求,最好还是将 DTO 和 VO 分开使用 。

(二)不同项目中的差异

需要注意的是,不同的公司、不同的项目对 VO、BO、PO、DTO、DO 的使用规范可能会略有差异 。有些项目可能会更注重代码的简洁性,在某些情况下会适当简化这些对象的使用;而有些项目可能对系统的扩展性和维护性要求较高,会严格遵循这些对象的设计原则和使用规范 。但无论如何,它们的核心逻辑和作用是一致的 。大家在实际开发中,不要过于死板地套用这些概念,要根据项目的具体需求和特点,灵活运用这些对象,让它们更好地为项目服务 。如果在项目中对这些对象的使用有一些特殊的规定或者约定,一定要和团队成员沟通清楚,确保大家对这些概念的理解和使用是一致的,这样才能提高团队的开发效率,减少因概念混淆而导致的代码问题 。

总结回顾

宝子们,到这里我们就把 VO、BO、PO、DTO、DO 这几个概念讲得透透的啦!咱们再来快速回顾一下 :

  • VO(View Object):视图对象,专为前端展示而生,它就像是前端的专属造型师,把数据整理得漂漂亮亮,让前端页面能够轻松展示。

  • BO(Business Object):业务对象,是业务逻辑层的核心担当,它把多个相关的 DO 或 PO 组合在一起,形成强大的业务处理单元,处理各种复杂的业务逻辑 。

  • PO(Persistent Object):持久化对象,和数据库表结构紧密相连,是数据库操作的得力助手,负责数据的增删改查,就像一个勤劳的小蜜蜂,在程序和数据库之间忙碌地传递数据 。

  • DTO(Data Transfer Object):数据传输对象,是不同层之间数据传输的桥梁,它减少数据传输量,提高传输效率,就像一个高效的快递员,安全、快速地把数据送到目的地 。

  • DO(Domain Object):领域对象,是对现实世界业务实体的抽象,它不仅有数据,还包含业务行为,是领域模型的核心,就像一个有血有肉的角色,在业务世界里有着自己的故事和行为 。

这些对象在软件架构中都起着至关重要的作用,它们相互协作,让系统能够高效、稳定地运行 。希望大家通过今天的学习,能够彻底搞清楚它们的区别和使用场景,在今后的项目开发中,能够正确、灵活地运用它们,让代码更加优雅、易维护 。如果还有什么疑问,欢迎在评论区留言,咱们一起讨论 !