面向对象编程内功心法系列四(聊一聊MVC)

198 阅读7分钟

1.引子

很多业务系统,我们都会基于分层架构设计来实现,就好比业界盛传的任何软件系统架构设计的问题,都可以通过增加一层中间层得到解决。你看这就是分层的重要性,分层设计思想是一种通用的设计思想。

那么对于我们大多数工程师来说,MVC是你我都熟悉且经典的分层设计实现模型。这里你可以先简单的回顾一下吗?是否你真的领会了MVC的神韵。

接下来,我想通过这篇文章给你分享我所理解的MVC模型,抛砖引玉地与你一起交流分享。

2.MVC模型

2.1.什么是MVC

我们说MVC是Model View Control的缩写,中文翻译是模型、视图、控制器。模型代表数据,视图代表前端展示,控制器代表用户交互。

今天我们忽略前端展示的视图,聚焦到系统后端架构设计上来。请你先简单的在脑海里重现一下你们的系统设计实现,按照标准的MVC分层设计,你们的后端系统是否包含有:

  • Control层:接口暴露层,也就是我们说的rest层,如果你是java工程师,喜欢spring框架,类上面有@Controller或者@RestController注解
  • Service层:业务逻辑处理层,负责业务系统业务处理,类上面有@Service注解
  • Repository层:持久化层,也就是我们说的数据访问(dao)层,类上面有@Repository注解,或者使用mybatis这样的持久层框架,直接是一个Mapper

你看这就是MVC分层架构设计代码层面的直观体现,代码结构层次非常简洁清晰。业界当前基于MVC分层模型开发,有两种主流的实现方式。一种是基于贫血模型,面向过程的开发方式;还有一种是基于充血模型,DDD领域驱动设计面向对象的开发方式

如何区分理解两种开发方式呢?如果你看了我们这个系列的第一篇文章(编程范式这一篇)。我们说到面向对象编程拥有四大基本特性:封装、抽象、继承、多态,它是以类和对象为基本单位来组织代码单元的,它把数据,与操作数据的方法封装绑定到一起

你需要关注黑色字体标记的这句话:数据,与操作数据的方法绑定到一起。这是面向对象编程。有了这个说明,我们再来看平常业务系统开发中是如何实现的,通常数据我们在业务层会通过BO对象进行封装,但是操作数据的方法是在Service中定义的,这就是典型把数据、与操作数据的方法分离了,不满足面向对象的特征,我们说它是面向过程编程范式

所以你看有时候我们通过面向对象编程语言,比如java,写的却是面向过程编程范式的代码。有点意外!不过你也不必太在意,我们说存在即合理,事实上基于贫血模型面向过程开发的方式,是我们当前应用得更多、更普适的开发方式。因为这种方式有一个优点:简单,快速上手。如果应用业务复杂度不高,它是最佳的实践方式,这种方式我们还可以称为薄BO,厚Service

反过来我们再来看基于充血模型,DDD领域驱动设计面向对象的开发方式。与薄BO厚Service面向过程开发的区别是,在DDD领域驱动模式下,它的特点是厚BO、薄Service。这句话怎么理解呢?我们会将Service中相关的业务操作,迁移到BO对象中。这就是厚BO、薄Service的内在含义,带来的最大收益即是实现了BO对象的复用性。但是它的成本是,对业务建模能力,工程师的技能水平要求都会比较高。如果应用的业务复杂度高,它是最佳的实践方式。

2.2.为什么要MVC

理解了什么是MVC,以及业界当前基于MVC编程实现的两种主流方式,我们再来看为什么要MVC?这里我简单的给你分享一下我的几点理解

  • 通过MVC分层设计实现,满足模块化、组件化的设计思想,满足单一职责原则,代码组织结构清晰,最大化实现了代码的复用性。比如我们有一个根据用户Id查询用户的需求,在Repository中实现一个getUserById的方法,所有其它需要根据用户Id查询用户的地方,都可以复用Repository中的getUserById方法
  • 隔离变化点。Controller层暴露接口,Service层处理业务逻辑,属于易变点(后端开发人员是不是经常埋怨需求变化),而Repository层持久化数据,CRUD操作相对比较稳定。通过MVC分层设计实现,最大化实现了隔离变化
  • 隔离关注点。通过MVC分层设计实现,每一层职责清晰,Control层只需要关心用户交互、参数校验;Service层只需要关心业务逻辑处理;Repository层只需要关心CRUD操作
  • 有效应对复杂性。我们说任何软件架构设计的目的,最终都是为了应对软件系统的复杂性。通过MVC分层设计实现,是有效应对软件系统复杂性的一种手段

2.3.关于VO、BO、ENTITY

到这里我已经给你分享了什么是MVC,以及通过MVC分层设计给我们带来的一些收益。在实际开发中,与MVC每一层都有相对应的数据对象,比如BO/VO/ENTITY等。我们一并简单做一个分享。

在实际的软件系统开发中,我们应该强烈不推荐一种做法,即一个数据模型对象走遍天下,什么意思呢?即是说不管在Repository层、Service层、还是Control层都是同一个ENTITY尽管大多数时候每一层的数据对象内容相似度非常高(即成员变量都差不多)。

我们应该推荐怎么做呢?每一层都应该有每一层的数据模型,一层一模型,即

  • Control层对应VO对象,这里的VO是Value Object
  • Service层对应BO对象,这里的BO是Business Object
  • Repository层对应ENTITY对象,这里的ENTITY与持久层的表模型

这样做有什么好处呢?我来举一个简单的例子,我想你就能明白了。比如操作存储用户信息的案例场景,一个需要登录认证授权的系统,用户信息中必然有用户名称,与密码基础信息。在数据库表中需要存储用户密码,即ENTITY中包含密码password成员变量;在BO中包含密码password成员变量;但是我们在展示用户信息的时候,是不能展示密码的,即VO中不能包含密码password成员变量

再举一个例子,比如我们是一个电商平台,订单系统订单记录,用户订单列表需要展示订单Id、商品名称、订单金额;但是像商家结算价格是千万不能暴露的,试想如果你跟商家的结算价格是100,卖给用户500,你把商家结算的价格暴露给用户了会怎么样?

因此,我们建议在实际开发中,最好遵循一层一模型,即Control层有自己的VO;Service层有自己的BO;Repository层有自己的ENTITY。是一种最佳的实践方式。