ThinkSNS源码分析(四):MVC架构

June 25, 2015 at 10:47 pm

本篇更多的是对之前的内容进行总结、概括,并从中提炼出相关的概念来,可能代码上就没有多少了。但是我相信对之前的内容进行复习、补充学习无疑是非常有帮助的。
在开篇中,我们分析了有关模板渲染的知识,所谓的模板,实际上是将程序中的一些逻辑和程序实际要显示出现的页面分离开来。现在我们给这种要显示的页面一个更统一的名称,视图[View]。这就是MVC中的V。我们再来回顾一下视图到底是什么。它主要是一个html页面,告诉我们最终的页面长成什么样子,唯一缺少的是里面要填入的字段,举例来说,视图中包含了欢迎用户的“欢迎您,XXX”这样的显示效果,但具体是哪个用户,这个XXX,则不属于视图的范畴。某个角色需要告诉视图,这个XXX到底是什么。
在第三篇中,我们分析了有关和数据库交互的知识,并且知道了所有和数据库的操作最终都涉及到XXXModel类。现在我们也给它一个统一的名称,模型[Model]。这就是MVC中的M。在上一篇中提到,M,通俗地来讲就是数据。当然,这是一种非常表象的理解,而且是个并不完整的理解。举一个简单的例子来说,RegisterModel也是一个模型,但是数据库里似乎并不需要Register这样一张表。更一般地,我们把所谓的模型解释为业务逻辑。如果对于业务逻辑这个概念并不够熟悉的话,我们暂时可以这样说,一个程序中,除了视图以外的部分都可以称为业务逻辑(注意这是一种理解的需要,但是并不能把模型和业务逻辑等同起来,后面会有更详细地解释)。业务逻辑代表了整个程序的运作原理,而最终呈现在用户面前的视图只是方便和用户交互用的部分而已。同样的功能,我们可以在终端界面的小黑框中完成,也可以借由GUI展现出华丽的效果。对于同样一个系统,交给不同的两个小组做,最终的界面也肯定不一样,但是内部的功能是一致的。
按照这样的逻辑,似乎可以称之为MV模式,那么MVC中的C起到怎么样的作用呢?C代表的是控制器[Controller],以注册为例,当用户填写完资料点击提交按钮进行注册时,我们需要干什么呢?我们确实有一套判断用户信息是否合理的业务逻辑,包括用户名是否已经被注册,用户名是否包含非法信息,资料是否伪造等等,如果用户信息合理,我们还要实实在在的把用户信息存放到数据库中。这些都放在了RegisterModel中,在这之前,等等,当用户提交了请求后,谁来负责决定要使用注册模型[RegisterModel]呢?针对这么大的系统问这样的问题似乎太难回答了,我们回到一个简单的问题里去。假设我们现在完成了一个字符界面的计算器程序,它很简单,只能执行加减乘除四项运算。用户首先输入两个数,然后再输入一个命令,1表示对两个数执行加法,2表示对两个数执行减法,3表示乘,4表示除,5表示退出。当用户输入3 4 1的时候,终端界面会显示如“Result: 7”这样的结果,这就是所谓的一个很丑的“视图”。Web系统中用户的请求实际上对应了传统程序中用户的输入。这里的1,2,3,4,5实际上就代表了实际要执行的业务逻辑,1代表执行加法,输出结果。这里的输入可能在一个input()函数里处理,input()函数得到输入后,写一个if else之类的语句if(cmd == 1) add();这样以调用加法。还需要使用output()这类的函数输出视图。这样类比之后你是否对控制器的功能有了更清晰的掌握?对,在执行具体数据相关的业务逻辑之前,对于大部分系统,尤其是Web系统,我们需要有可以处理用户输入的逻辑,依据用户的输入执行具体的业务逻辑,这就是控制器的作用,可以认为它根据用户请求来“做决定”。
这是否和之前所说的“一个程序中,除了视图以外的部分都可以称为业务逻辑”相矛盾呢?实际上并没有。控制器本身也是广义上业务逻辑的一种,这是不可否认的。但是这个业务逻辑较为特殊,它本身并不处理具体的数据,而是起到找到处理数据工作人员的作用的人,看起来就像是经理,它本身不干活,而是让具体的员工来干活。也就是说,控制器处理的是数据无关的业务逻辑,而模型处理数据相关的业务逻辑。好了我们扯回来,回到注册这个例子。
我们在第二篇中讲过,当用户点击“提交注册”按钮之后,首先要有路由的过程,来找到具体的模块的具体的小模块,即所谓的Module和Action。现在我们就可以站在更高的角度看这个问题了。我们口口声声说的模块,实际上仅仅是针对这个模块的控制器,而每个action就可以理解为一个个小型的控制器。当用户提交请求后,通过路由系统,找到某个模块相关的控制器,再细化到具体的操作[Action],而这个action再来决定要使用哪个业务逻辑。还是以“提交注册”为例,首先交由路由,得到最终位置RegisterAction这个控制器的一个操作doStep1()中。进入之后,我们使用$this->register_mdel来判断用户名是否有效,邮箱是否有效,密码是否有效等等,如果都没有问题,我们使用$this->_user_model->add(...)方法执行用户的注册,即写入数据库,最后显示出“注册成功”字样的视图。
扯了这么多,相信你对MVC这个概念已经有了初步的认识,而且对于M, V, C这三个元素也能说出点一二三来了。下面我们以问答的形式来阐述一下MVC架构模式,并且分析一下这么做的好处,或者说不这么做的缺陷,解决对于MVC模型仍然的疑问。
问题一,MVC是什么,在ThinkSNS中对应的是哪一部分?MVC是一种针对计算机软件交互部分的架构模式,对于Web这种重交互的应用,它通常也做为整个系统的架构模式(如果你熟悉Java的图形界面开发,你就会发现Java标准API提供的图形控件都以MVC的方式实现,从那个角度来看,MVC只是你的程序中自己都可能不怎么重视的很小部分)。它将交互部分分成三个部分,模型、视图和控制器。在ThinkSNS中,每一个html模板都对应了一个视图,每一个XXXModel类都对应了一个模型,每一个XXXAction类都对应了一个控制器。
问题二,为什么要有V,如果没有V会发生什么事?V也就是我们所说的视图,它代表了界面。将界面和程序的其它部分分离开来是很早以前就有的一种编程思想。对于一份数据来说,它可以有很多的表现形式,而且这个表现形式可能经常发生变化,如果把表现形式糅杂在程序中,将来改动的时候就会非常复杂。这里举两个极端的例子来说明分离界面和程序其它部分的重要性。第一个例子是,对于同样的数据,电脑版和手机版的网页显示的内容显然不一样,当然还会有手机app,都要有不同的展现方式,它们的数据一样,仅仅是表现形式不一样,如果两者是糅杂在一起的,那么改动起来非常困难。第二个例子是,如果我们网数据库里存储了一个代表界面的html代码,那么将来这个程序恐怕是不可能转化成CS模式了,数据库里的数据完全无法复用。这些例子都表明,对于大型的系统来说,界面和程序的其它部分一定要分开。
问题三,为什么要有C,如果没有C会发生什么事?C也就是我们所说的控制器,它接收用户的请求,并调用具体的模型进行处理。我们前面说过,事实上C和M都属于业务逻辑,那么如果C和M揉合在一起会怎么样呢?当接收到用户请求后,直接交由M来处理。M需要首先解析用户的请求,接着需要和数据库交互,最终还要将结果告诉视图,渲染出最终的结果,可以说是极大任于一身。一旦数据库的表结构因为某种原因(如效率太差重新设计)发生了变化,天呐,我们又要在茫茫码海中捞针,去找和数据相关的业务逻辑。事实上,无论数据的存储方式、获取方式发生任何变化,只要最终的数据本身没有变化,程序中的这部分业务逻辑,尤其是解析用户请求、展示最终视图这部分逻辑,都不会发生变化。C和M糅杂在一起,改动起来非常麻烦。
MVC架构的出现解决了上述的三个问题,使得程序结构显得更加清晰,设计分工显得更方便。但是MVC架构本身并没有对V如何设计,M如何设计提出意见。除了C部分由于要承担M和V的通讯工作而基本统一外,V的设计和M的设计都是值得去思考的。关于V的设计不属于本系列的讨论范围,那更多偏向于前端设计。如前面反复说过的,M是和数据相关的业务逻辑,主要扯到的部分是数据库。会在番外篇中对ORM的内容进行讨论。
ThinkSNS整体的Web框架已经基本铺好了,下一篇我们暂时走向前端,看看在ThinkSNS中,如何对javascript代码进行设计和管理。

お楽しみに!