系统分层设计中的实体类(POJO,PO,DO,DTO,BO,AO,VO,QUERY)

1.存在的理由

MVC设计模式作为现如今最主流的编程方式,提供了一种 表示层调用控制层,控制层调用业务层,业务层调用数据访问层 的代码结构。其目的是为了对一个系统进行分层解耦,使设计出来的系统拥有但不仅限于写好了一套Service,便可提供服务给多方前端使用(例如 管理后台的界面,第三方合作方的接口调用,作为RPC服务为本公司其他业务线提供服务),同时提供了底层数据库技术迁移的能力,例如想从oracle迁移到mysql中,那便只需要处理DAO层的代码便可。

有了分层,便有了层次间的调用(例如 orderService.createOrder()),有了层次间的调用,便有了入参和出参。在做比较简单的只涉及到增删该查的系统的时候,入参跟出参往往都是同一个封装对象,并且这个对象是数据库的数据的一个抽象对象,容易造成一种错误的认知,那就是传参不都这么做吗,直接一个对象传过去,再拿出自己需要的数据进行处理就行了。

但是理论上来说,接口调用时的入参和出参,应该是包含且仅包含该接口所负责功能的必须或可选参数的。这也可以参考下设计模式中的 迪米特法则(最少知道原则)以及单一职责原则。

既然接口间的调用(理论上)都需要为其封装对应的入参和出参,那在各个层次之间有着那么多的入参以及出参,如果不加以区分,会增加一定的混淆概率。而对位于不同层次位置,不同功能的实体类,使用不同的概念进行区分,便可解决这个问题。

这也是本人认为的实体类分成不同的概念存在的原因之一——对实体类进行归类约束,以区分界限

2.实体类的大体划分

现在实体类的划分可以分得很细,并且由于不同时代的实体类划分往往可能具备类似的简写,容易给人造成误解,这里我列举一些在百度上能搜到的大多数的实体类划分。

POJO(Plain Ordinary Java Object) 简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称。虽然是属于EJB中的一个概念,但是基本上可以用来描述所有的其他类型实体类。对于EJB的概念,可以参考https://blog.csdn.net/xufei512/article/details/52703113

PO(Persistant Object) 持久对象,可以看成是与数据库中的表相映射的java对象。最简单的PO就是对应数据库中某个表中的一条记录,多个记录可以用PO的集合。PO中应该不包含任何对数据库的操作。在某些归类约束中,也把他叫做DO(Data Object),也可叫做Entity

DO(Domain Object) 领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。大多数情况下是与PO一一对应的。但是他还可以表示多个PO之间的业务关系

BO(Business Object) 业务对象,封装业务逻辑为一个对象(可以包括多个PO,DO,通常需要将BO转化成PO,才能进行数据的持久化,反之,从DB中得到的PO,需要转化成BO才能在业务层使用)。

关于BO主要有三种概念

  1. 只包含业务对象的属性;
  2. 只包含业务方法;
  3. 两者都包含。

DTO(Data Transfer Object) 数据传输对象,和TO(Transfer Object)有异曲同工之妙

  1. 用在需要跨进程、跨系统层次(Service -> Controller)、远程传输时,它不应该包含业务逻辑。
  2. 比如一张表有100个字段,那么对应的PO就有100个属性(大多数情况下,DTO 内的数据来自多个表)。但view层只需显示10个字段,没有必要把整个PO对象传递到client,这时我们就可以用只有这10个属性的DTO来传输数据到client,这样也不会暴露server端表结构。到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为VO。

AO(Application Object) 应用对象,在 Web 层与 Service 层之间抽象的复用对象模型,极为贴近展示层,复用度不高。使用率感觉也不高,可以忽略

VO(value object值对象 / view object表现层对象)

  1. 主要对应页面显示(web页面/swt、swing界面)的数据对象。
  2. 可以和表对应,也可以不,这根据业务的需要。

QUERY 数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输。可以直接理解成各个层次接口的请求参数的一个封装对象。

DAO (Data Access Object) 数据访问对象,是对于数据库中的数据做增删改查等操作的代码。这里不把Dao当作实体类

3.实体类的大概区别

VO与DTO的区别

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

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

DTO与DO的区别

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

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和Contacts两个DO数据合并到一张数据表中。反过来,如果一本图书Book,有一个属性是封面cover,但该属性是一副图片的二进制数据,而某些查询操作不希望把cover一并加载,从而减轻磁盘IO开销,同时假设ORM框架不支持属性级别的延迟加载,那么就需要考虑把cover独立到一张数据表中去,这样就形成一个DO对应对个PO的情况。

PO的某些属性值对于DO没有任何意义,这些属性值可能是为了解决某些持久化策略而存在的数据,例如为了实现“乐观锁”,PO存在一个version的属性,这个version对于DO来说是没有任何业务意义的,它不应该在DO中存在。同理,DO中也可能存在不需要持久化的属性。

BO与DO的区别

首先BO其实跟DO是有着相似的一个点,那就是都可以描述不同PO之间的业务关系,并能封装适度的业务处理逻辑。不同的点在于DO更加偏向于描述业务关系,例如学校DO对象里面存在学校本身的PO以及多个班级PO,表示一个学校以及学校下的多个班级,其描述了学校与班级的一个一对多的业务关系。而BO而更加偏向于业务处理逻辑封装,其描述了一个业务的处理流程,该处理流程可能会处理到位于多个领域的多个DO,例如购物订单创建的逻辑包含了创建订单DO,创建预扣库存DO,创建付款单DO。当然你也可以这么理解,他们就是同一个东西,用于封装不同PO之间的业务关系以及其业务处理逻辑。

4.实体类的实际使用

在一个完成单表增删该查的管理系统中,一个PO,往往可以完美担任DO,BO,DTO,VO,QUERY,这时候,其实就没必要特意设计一套属性值其实一模一样的PO,DO,BO,DTO,VO,QUERY了,也就是说,各种不同类型的实体类之间,可以完成一个由底层往高层(越贴近现实事物的实体类越底层)的一个替换的,而省去一些复制粘贴的成本。例如DTO(高层实体类)设计出来的时候与DO(底层实体类)的属性一模一样,那么完全其实就可以把DO当作DTO来使用,但是这时,其实DO已经不是作为DO在发挥作用了,而是一个类命名规范为DO的DTO。

实体类的概念有多种,同一种概念以不同的视角来看,也会有多种不同的解释,在实际使用中,认为哪一种概念正确并不重要,也不应该拘泥于哪一个实体类绝对不应该出现在哪里,关键是实际应用中适合自己项目的需要。

参考

https://cloud.tencent.com/developer/news/309461

http://blog.sina.com.cn/s/blog_67263f510100i7t4.html

https://blog.csdn.net/Ani521smile/article/details/51460382

阿里巴巴 Java 开发手册

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值