欢迎光临散文网 会员登陆 & 注册

聊聊软件的分层设计思想(1)基础

2019-05-14 18:21 作者:_Kaizen_  | 我要投稿

分层思想是一种软件工程方法中最常用的思想,本系列力图通过直白的语言来探讨这种思想背后的故事。

说到软件工程,很多人学软件工程就只是在学校里学了一堆过时的技术,踏上工作岗位后却发现大部分的技术都很难用得上,也因此很多人都在质疑软件工程的价值。

这种现象是有原因的,软件工程是一门年轻的学科,实际开发中的工程方法一直在变化,很多时候当工程方法形成教材的时候就已经过时了;另外因为软件不像其他领域有物理空间、自然法则等的约束(可能唯一的约束就是摩尔定律了),夸张点说拥有无限的可能性,而在实际应用中大部分软件都是直接面向“人”的问题的,任何问题只要一有“人”的因素,复杂度就会成倍的增加,因此也就很难像其他领域的工程学一样把大部分情况都照顾到并形成一致的规范。所以想要通过学习一些规范就能成为一名合格的工程师是不可能的。

软件工程中缺少普适性的规范(当然现在也正在不同的细分领域中逐步探索),很多时候现有的规范和方法都只能当作一种参考,在实践中更需要创造性,如果只会死记硬背所谓的规范而不去理解背后的思想是没有意义的。

从MVC说起

MVC是一种重用户交互的软件架构模式,基于MVC的思想还衍生出MVP、MVVM等模式,当然你也可以说是架构风格、框架,这些称呼无所谓,总之这是一种分层思想的典型产物。

提到MVC很多人可能会想到不同的技术,后端的有SSH、SSM、ASP.NET MVC,前端的有AngularJS、VueJS,以及ReactiveX系列的框架。

当然这篇不说具体技术,我只想通过MVC来聊聊为什么?我们为什么需要分层?

为什么需要MVC,或者说用MVC有什么好处,很多书本中都给出了一些标准答案,大多是围绕着“复用”这个关键词进行解释的;还有一个解释是通过明确的分层能够使模型、视图和控制器能够独立的变化,修改其中一层中的代码不会影响其他层的代码;在一些老的书本中还会说因为M、V、C各自独立以后可以通过替换视图(V)层的方式把 C/S 架构的程序重构成 B/S 架构。

但我对这些解释很不满意,比如,通过替换视图层来把 C/S 架构的程序重构成 B/S 架构这种说法根本是无稽之谈。且不说根本不会有哪个MVC框架会同时支持C/S、B/S两种架构,即便是有,在实际工作中也很难遇到这种需求,即便在实际工作中真的遇到了这种情况,那这个时候关注这种代码级别的问题就好比一把火把房子烧了还在考虑今晚怎么刷碗一样。

第二种说法稍微好一些,模型、视图和控制器能够独立的变化,看上去很美,但实际上真的有这么大作用吗?我们看下常见的变更会有哪些:

  1. 调整界面布局

  2. 调整数据的显示方式

  3. 调整数据字

  4. 调整处理逻辑

对于中小型的项目我们可能会有如下开发模式:

  1. MVC

  2. RAD(基于事件的类WinForm、WebForm框架)

  3. 混合编码(类JSP)

三种模式在面对这四种变更:

通过这个表格,能看得出来MVC的优势在哪吗?

再来看看“复用”,似乎“复用”是一个很好的理由。但是稍微有点经验的人会明白,说这种与业务紧密相关的代码根本不可能复用有点绝对,但也绝不是一件容易的事。实际上最容易复用的代码大多是工具性质的,比如各种日期、字符串、数组处理,格式转换等代码。

当然分层之后模型(M)层在项目内部还是可以实现一定程度的复用的,但这就是用MVC的理由了吗?

不,在我看来上面的这些理由都不是最关键的。

分层真正的理由

要了解MVC乃至分层思想的真正理由,我们首先要明白一点,不是程序需要分层,而是我们人类需要分层。这是因为我们的大脑的局限性:我们在思考问题的时候所用到的工作记忆容量小的可怜。工作记忆相当于电脑中的内存(RAM),在思考问题的时候大脑要从长期记忆(相当于硬盘)中提取信息放入工作记忆,到底有多少小呢,最早的说法是7±2个事物,但后来的研究“成功”的把这个数量降低到了4~5个,感兴趣的的同学可以百度一下这篇文章:

《神经现实:我们短期只能记4、5个事物?是什么限制了工作记忆的容量?》

那为什么我们还能处理复杂的问题呢?心理学家告诉我们,我们大脑会通过组块来提高工作记忆的效率,组块也就是把相关的多个信息组成一个信息块,这样的信息块仍然是占用一格工作记忆,通过这种打包的方式,我们就可以同时处理更多的信息了。

为了照顾人的大脑的这个缺陷日常生活中有很多地方都做了便于组块的设计,比如1位电话号码就是分了3组数字每组数字都不超过4位数,像这样:138 0000 0000。

看到这里,聪明的小伙伴已经明白了我为什么说我们人类需要分层了。分层最关键的作用就是帮助我们对问题进行切分和组块。键盘鼠标等外设有个“人体工学”的说法,人体工学的目的就是使工具尽量的符合人体自然形态,降低使用疲劳、提高效率。相比之下分层思想可以说是一种“人脑工学”。

其实从这里反推回其他软件工程方法,最典型的就是面向对象,面向对象三个特征封装、继承、多态,三个当中最重要的特征就是封装,封装的关键作用就是方便人脑对零散的信息组块,当然继承和多态可以更进一步的提高信息密度的作用,只是这两个的作用不如封装来得那么直接。

分层的原则

前面说了分层的理由,其实分层的原则也是要从分层的理由出发的。分层的理由是为了方便信息组块,那分层的原则就是分出来的层次能够符合大多数人直觉。

这个原则可操作性并不强,因为这种判断力并不是能够轻易获得的,创造分层模式其实就是在构建一个新的逻辑和话语体系,需要创造者有足够的理论基础和实践经验。

但即便不需要创造新的分层模式,只是应用已经存在的分层模式,也是需要这样的判断力的。

拿很多新人在学习阶段经常拿来练手的图书借阅管理系统举例(话说,每年各高校和培训机构开发的图书借阅系统也有个几万个了吧😂),假设系统采用MVC框架,且增加了Service层。考虑这个场景:会员能够在借书模块中浏览图书档案模块中的数据,选出来的图书能否借阅需要结合会员等级(决定了能同时借多少本)和图书状态进行判断,确定借出后保存借阅记录,这些功能接口该如何安排在各个模块的各个层次中?

我见过很多人会照葫芦画瓢的各自写出各个实体的数据访问层代码,Service 层只是简单的调用数据访问层 CRUD 代码,然后在 Controller 中写出相关逻辑判断,然后调用 View 层渲染显示。结果是薄薄的 DAO 层和 Service 层,厚重的 Controller 层。

相对合理的方式是把各个功能分布在 Service 层中,如下所示:

这种程度的重构,相信即便是新人也能做的到,再考虑以下场景:

  1. 会员通过个人控制面板页面可以查询自己的借阅历史

  2. 管理员通过图书档案的页面可以查询图书的借阅历史

Service层的代码应该放在哪个模块?借阅记录Service?还是会员信息Service和图书档案Service?

从这里开始就需要斟酌了,以主体为中心的思维方式会倾向于把代码分别放在会员信息Service和图书馆档案Service中,以客体为中心的思维方式会倾向于把代码都放在借阅记录Service中。

那到底哪种方式更合理一些呢?其实两种方式都有一定的合理性,这是最麻烦的。

无论选择哪种方式都必须要保证逻辑的一贯性,在这里用以客体为中心的思维方式把借阅历史都放在借阅记录 Service 中似乎最能够保持逻辑的一贯性。

但是在项目达到一定规模的时候还要注意设计要能便于分工,如果图书档案和会员信息两个模块分给不同人来做,那么他们就会面临可能会同时修改借阅记录模块 Service 的情况(其实这时候就引出了面向接口编程的理由)。

在实际设计中,只有一个大原则是不够的,还要把其他因素都列举出来并排序,然后再进行取舍。

小结

本篇通过最常见的MVC模式简单讨论了一下分层思想的原因和原则,在软件设计中分层思想在功能设计、软件的功能横向扩展和性能纵向扩展等领域都有用到,我会在后续的文章中进一步讨论。


聊聊软件的分层设计思想(1)基础的评论 (共 条)

分享到微博请遵守国家法律