每个接口 一个vo_设计模式之美二十二:如何实现一个遵从设计原则的积分兑换系统?...

本文介绍了积分兑换系统的设计思路,强调了分层开发(MVC)的重要性,如代码复用、隔离变化、关注点分离等。探讨了BO、VO、Entity存在的意义,解释了为何不合并为单一数据对象,以及如何处理代码重复问题。此外,还讨论了数据对象转化工具在对象转化中的作用,以及在设计中遵循的设计原则和思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

王争《设计模式之美》笔记

业务开发包括哪些工作?

方面:接口设计、数据库设计和业务模型设计(也就是业务逻辑)。

数据库和接口的设计非常重要,一旦设计好并投入使用之后,这两部分都不能轻易改动。先数据库和接口设计,业务逻辑代码侧重内部实现,不涉及被外部依赖的接口,也不包含持久化的数据,所以对改动的容忍性更大。

针对积分系统,我们先来看,如何设计数据库。

3468567fd23b8698c91386ed0414d7d3.png

接下来,我们再来看,如何设计积分系统的接口。

68757321f0bba141b6045dca84c84ead.png

最后,我们来看业务模型的设计。

业务相对比较简单,所以,选择简单的基于贫血模型的传统开发模式就足够了。

为什么要分 MVC 三层开发?

几点原因:

1. 分层能起到代码复用的作用

2. 分层能起到隔离变化的作用

分层体现了一种抽象和封装的设计思想。比如,Repository 层封装了对数据库访问的操作,提供了抽象的数据访问接口。基于接口而非实现编程的设计思想,Service 层使用 Repository 层提供的接口,并不关心其底层依赖的是哪种具体的数据库。当我们需要替换 数据库的时候,比如从MySQL到Oracle,从 Oracle到Redis,只需要改动 Repository层的代码,Service层的代码完全不需要修改。

除此之外,Controller、Service、Repository 三层代码的稳定程度不同、引起变化的原因不同,所以分成三层来组织代码,能有效地隔离变化。比如,Repository 层基于数据库表,而数据库表改动的可能性很小,所以 Repository 层的代码最稳定,而 Controller 层 提供适配给外部使用的接口,代码经常会变动。分层之后,Controller 层中代码的频繁改 动并不会影响到稳定的 Repository 层。

3. 分层能起到隔离关注点的作用

Repository 层只关注数据的读写。Service 层只关注业务逻辑,不关注数据的来源。 Controller 层只关注与外界打交道,数据校验、封装、格式转换,并不关心业务逻辑。三层之间的关注点不同,分层之后,职责分明,更加符合单一职责原则,代码的内聚性更好。

4. 分层能提高代码的可测试性

后面讲单元测试的时候,我们会讲到,单元测试不依赖不可控的外部组件,比如数据库。分层之后,Repsitory 层的代码通过依赖注入的方式供 Service 层使用,当要测试包含核心业务逻辑的 Service 层代码的时候,我们可以用 mock 的数据源替代真实的数据库,注入到 Service 层代码中。

5. 分层能应对系统的复杂性

所有的代码都放到一个类中,那这个类的代码就会因为需求的迭代而无限膨胀。我们知道,当一个类或一个函数的代码过多之后,可读性、可维护性就会变差。那我们就要想办法拆分。拆分有垂直和水平两个方向。水平方向基于业务来做拆分,就是模块化;垂直方向基于流程来做拆分,就是这里说的分层。

BO、VO、Entity 存在的意义是什么?

针对 Controller、Service、Repository 三层,每层都会定义相应的数据对象,它们分别VO(View Object)、BO(Business Object)、Entity,例如UserVo、UserBo、UserEntity。在实际的开发中,VO、BO、Entity可能存在大量的重复字段,甚至三者包含的字段完全一样。在开发的过程中,我们经常需要重复定义三个几乎一样的类,显然是一种重复劳动。

相对于每层定义各自的数据对象来说,是不是定义一个公共的数据对象更好些呢?

实际上,我更加推荐每层都定义各自的数据对象这种设计思路,主要有以下3个方面的原因。

VO、BO、Entity并非完全一样。比如,我们可以在 UserEntity、UserBo 中定义 Password 字段,但显然不能在 UserVo 中定义 Password 字段,否则就会将用户的密码暴露出去。

VO、BO、Entity 三个类虽然代码重复,但功能语义不重复,从职责上讲是不一样的。所以,也并不能算违背 DRY 原则。在前面讲到 DRY 原则的时候,针对这种情况,如果合并为同一个类,那也会存在后期因为需求的变化而需要再拆分的问题。

为了尽量减少每层之间的耦合,把职责边界划分明确,每层都会维护自己的数据对象,层与层之间通过接口交互。数据从下一层传递到上一层的时候,将下一层的数据对象转化成上一层的数据对象,再继续处理。虽然这样的设计稍微有些繁琐,每层都需要定义各自的数据对象,需要做数据对象之间的转化,但是分层清晰。对于非常大的项目来说,结构清晰是第一位的!

既然VO、BO、Entity不能合并,那如何解决代码重复的问题呢?

我们可以将公共的字段定义在父类中,使用继承或者组合。

代码重复问题解决了,那不同分层之间的数据对象该如何互相转化呢?

Java 中提供了多种数据对象转化工具,比如 BeanUtils、Dozer等,可以大大简化繁琐的对象转化工作。如果你是用其他编程语言来做 开发,也可以借鉴 Java 这些工具类的设计思路,自己在项目中实现对象转化工具类。

VO、BO、Entity 都是基于贫血模型的,而且为了兼容框架或开发库(比如MyBatis、 Dozer、BeanUtils),我们还需要定义每个字段的 set 方法。这些都违背 OOP 的封装特性,会导致数据被随意修改。那到底该怎么办好呢?

前面我们也提到过,Entity 和 VO 的生命周期是有限的,都仅限在本层范围内。而对应的 Repository 层和Controller层也都不包含太多业务逻辑,所以也不会有太多代码随意修改数据,即便设计成贫血、定义每个字段的 set 方法,相对来说也是安全的。

总结用到的设计原则和思想

260b902ad06beea2e7658a02876b4249.png

重点回顾

需要掌握的重点内容。

1. 为什么要分MVC 三层开发?

5 点原因:

  • 分层能起到代码复用的作用
  • 分层能起到隔离变化的作用
  • 分层能起到隔离关注点的作用
  • 分层能提高代码的可测试性
  • 分层能应对系统的复杂性

2.BO、VO、Entity 存在的意义是什么?

从设计的角度来说,VO、BO、Entity 的设计思路并不违反 DRY 原则,为了分层清晰、减少耦合,多维护几个类的成本也并不是不能接受的。但是,如果你真的有代码洁癖,对于代码重复的问题,我们可以通过继承或者组合来解决。

如何进行数据对象之间的转化?最简单的方式就是手动复制。当然,你也可以使用Java中提供了数据对象转化工具,比如 BeanUtils、Dozer 等,可以大大简化繁琐的对象转化工作。

尽管 VO、BO、Entity 的设计违背 OOP 的封装特性,有被随意修改的风险。但 Entity 和 VO 的生命周期是有限的,都仅限在本层范围内,相对来说是安全的。Service 层包含比较多的业务逻辑代码,所以 BO 就存在被任意修改的风险了。为了使用方便,我们只能做一些妥协,放弃BO的封装特性,由程序员自己来负责这些数据对象的不被错误使用。

3. 总结用到的设计原则和思想

从表面上看,做业务开发可能并不是特别有技术挑战,但是实际上,如果你要做到知其然知其所以然,做到透彻理解、真的懂,并不是件容易的事情。深挖一下,你会发现这其中还是蕴含了很多设计原则、思想和模式的。

课堂讨论

  1. 上节课中,我们讲到,下层系统不要包含太多上层系统的业务信息。但在今天的数据库设计中,积分明细表中credit_transaction 中包含 event_id,channel_id 这些跟上层业务相关的字段,那这样的设计是否合理呢?
  2. 我们经常说,修改和查询不要耦合在一个接口中,要分成两个接口来做。赚取积分和消 费积分接口返回积分明细ID,这样的接口设计是否违背单一职责原则呢?是不是返回 void 或者 boolean类型更合理呢?

参考:https://time.geekbang.org/column/intro/250?code=gLit0LpsKZQ6vOVqS1htGOSAKYLCYeMuklw2dwajH-4%3D

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值