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的内容进行讨论。 […]

hibernate入门(一):编写第一个hibernate程序

June 21, 2015 at 4:35 pm

  本系列介绍hibernate使用过程中所遇到的事情之类的。虽然加上了标号,但是并不保证会有第二篇,而且很有可能会没有。第一,我不是一个java程序员,用不着整天和这些东西打交道;第二,初次体验就这么不好的东西我并不打算再用了。   hibernate是一个ORM框架,即所谓的对象关系映射,把数据库中的每一张表表现为一个类,每一条记录表现为一个对象。总而言之只是一个方便人们处理数据库的东西而已。没想到这样一个东西,要写成一个"HelloWorld"型的入门程序要写那么多的配置,对于一个未入门的渣渣码农而言,实在是有够烦。先是试图找一些简单的书籍,但是hibernate现在出到4了,我也不打算用旧版本,4对应版本的入门书籍粗粗找了下没找到,所以最终就决定以官网提供的文档(4.3.10Final)的第一章"The first Hibernate Application"为入门指南写第一个程序(链接在这)。好了废话不多说了进入正题。 这个版本的文档已经要求包括hibernate和相关的依赖包以maven的方式使用了。如果你还不知道maven,本文可能就无法进行下去了,所以还是去先学一下吧,maven我倒感觉是个好东西,虽然只是入了个门,大体就是一个方便下载jar包的程序。文档首先告诉我要使用它提供的pom.xml,我的做法是直接maven建立了一个普通项目,然后再把pom.xml的内容替换掉,反正也没差。但是作为4.3.10Final的文档,却是一点都不Final,要改的地方好多,(或许和maven版本有关也说不定,本文使用的是当前的最新版3.3.3,不过最新版的文档为什么不是用最新版的maven?至少也得顾及一下最新版用户的感受吧?)要改的地方主要是给所有依赖包提供版本,即<version>...</version>标签,另外还有个地方是它文档里说使用的数据库是一种叫hsqldb的东西,我一开始以为内置了,可是报找不到类的错,所以在maven里我还是给配上了,然后就按照编译器说的真的成功了...还一处不提也罢,总之 最后形成的pom.xml结果如下: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.hibernate.tutorials</groupId> <artifactId>hibernate-tutorial</artifactId> <version>1.0.0-SNAPSHOT</version> <name>First Hibernate Tutorial</name> <build> […]

ThinkSNS源码分析(三):数据库交互与ORM初步

June 17, 2015 at 5:23 pm

Web系统最终都无可避免的和数据库打交道。虽然现在市面上存在的数据库类型很多,但对于Web系统而言,主流的还是传统的关系型数据库。ThinkSNS仅提供了对MySQL数据库的支持。好了,废话不多说,先看代码吧。 正如上一篇所约定的那样,我们以注册模块为例分析ThinkSNS对数据库的操作,相信你已经熟悉要在RegisterAction.class.php里找寻相关的代码了。粗略地阅读代码就会发现$_register_model和$_user_model这两个成员变量出现的场次非常多,和数据库的交互似乎都经由它们来完成。我们可以找到它们的实例化和使用示例代码: <? $this->_register_model = model('Register'); $this->_user_model = model('User'); $user_info = $this->_user_model->getUserInfo($this->uid); 这里的model函数是来自于function.inc.php的全局函数,它实例化一个model目录下的RegisterModel.class.php, UserModel.class.php的一个对象,而RegisterModel, UserModel均继承自核心类Model。在分析Model是什么之前,我们先沿着代码的思路往下看,看看Model究竟是如何执行数据库操作的。我们以UserModel的getUserList为例,这个方法显然返回指定条件的用户列表。我们可以找到如下查询语句: <? $list = $this->where ( […]

ThinkSNS源码分析(二):路由

June 10, 2015 at 11:20 am

在ThinkSNS中,我们访问的地址通常都是"index.php?app=...&mod=...&act=...",看起来都是访问同一个页面,但是根据参数不同等会呈现不同的状态,这就是所谓的路由。路由可以理解为根据地址栏里的地址,通过解析之后,生成最终所需要的页面的过程。如果完全没有路由功能,PHP页面也是可以工作的,我们只要访问特定的php文件就可以了,比如login.php对应了登录页面,add_friend.php对应了增加朋友页面。这样做明显的一个缺点是会造成页面的一个膨胀,而且相互关联的页面之间并不能很好地组织在一起,只能相互平行。不仅如此,不使用路由的方式给了用户过多的内部结构,无法避免用户对页面的直接访问,容易给恶意用户有机可乘。事实上路由正如实际的路由器一般,统一接受用户请求,然后分发到指定地方,在web中即为生成指定页面,路由中枢可以进行包括过滤等功能。 事实上很多HTTP服务器本身也提供了路由功能,如Apache服务器中的.htaccess文件。在ThinkPHP中,路由功能是由PHP代码自身实现的。大部分的访问地址均为"index.php?app=$app&mod=$mod&act=$act"的方式。其中$app指代具体的应用,$mod指代具体的模块,$act指代具体的操作。如上一篇中所提及的,这三者的关系是从大到小,即这个地址的意义为执行$app应用中的$mod模块的$act操作。 毫无疑问,由于是在代码层面上实现的路由,上述地址第一步经由HTTP服务器将我们带到index.php文件中。index.php中要进行路由,就要对传入的$app, $mod, $act三个参数进行解析,在index.php载入内核OpenSociax.php时,保存了这三个参数,代码如下: <? if(!isset($_REQUEST['app']) && !isset($_REQUEST['mod']) && !isset($_REQUEST['act'])){ $ts['_app'] = 'public'; $ts['_mod'] = 'Passport'; $ts['_act'] = 'login'; […]