MVC是一种设计模式(Design pattern),也就是一种解决问题的方法和思路, 是上世纪80年代提出的,到现在已经颇有历史了。 MVC的意义在于指导开发者将数据与表现解耦,提高代码,特别是模型部分代码的复用性。
MVC不仅仅存在于Web设计中,在桌面程序开发中也是一种常见的方法。MVC的出现已经有一段历史了。 记得我最早了解到MVC的时候,是在Microsoft的Visual C++ 中的MFC中。 当时年少无知,以为是MFC中特有的东西。后来随之不断学习,才发现自己的天真。 所以说,学得越多,就越觉得自己无知。越觉得自己无知,就越懂得敬畏和谦逊。 从这个角度讲,同学们,最好不要看不起谦逊的人。
有个这么一个段子,说一天A君在圈内聚会时,朋友介绍了另一个人B君互相认识。 聚会场合嘛,这很正常,也很普遍。于是AB君小聊了一下。按国人的习惯,A君就问了“先生在哪高就?”。 B君只说了句,“谈不上高就,炒炒股。” “哦,原来是炒股的。”A君心想,虽没觉得什么不对,但心理觉得B有点low,只是没说破,也没表现出来。 过后了一段时间,一次偶然机会,发现原来B君是国内某上市公司的二股东,身家过亿。 人家没说慌,确实是炒股的……
话说远了,我们还说正题。MVC是三个单词的缩写:Model, View, Controller。 MVC是一种设计模式,目前几乎所有的Web开发框架都建立在MVC模式之上。 当然,最近几年也出现了一些诸如MVP, MVVM之类的新的设计模式。 但从技术的成熟程度和使用的广泛程度来讲,MVC仍是主流。
Yii是一个Web框架,从Web开发的分工来讲,Yii的开发工作中,承担后端的内容多一些,毕竟主要就是PHP开发。 前端主要是在HTML、JavaScript、CSS上进行开发,然后通过Yii把前端的内容管起来,如通过Assets等。 这一章要讲的MVC,主要是针对后端的。 前端的MVC严格来讲不属于Yii的范畴,这里我们就不作过多介绍。 如果想了解前端的MVC,可以看看Backbone.js Angular.js等前端框架。
MVC是模型(Model)、视图(View)、控制器(Controller)3个单词的缩写。 下面我们从这3个方面来讲解MVC中的三个要素。
对于MVC中三者的划分并没有十分明晰的定义和界线。MVC设计模式只是一种指导思想, 让你按照model, view, controller三个方面来描述你的应用,并通过三者的交互,使应用功能得以正常运转。
其中,View的部分比较明确,就是负责显示嘛。一切与显示界面无关的东西,都不应该出现在View里面。 因此,View中一般不会出现复杂的判断语句,不会出现复杂的运算过程。 对于PHP的Web应用而言,毫无疑问,HTML是View中的主要内容。这是关于View的几个原则:
对于Model而言,最主要就是保存事物的信息,表征事物的行为和对他可以进行的操作。 比如,Post类必然有一个用于保存博客文章标题的title属性,必然有一个删除的操作,这都是Model的内容。 以下是关于Model的几个原则:
对于Controller,主要是响应用户请求,决定使用什么视图,需要准备什么数据用来显示。 以下是有关Controller的设计原则:
在MVC中,Model排第一,是有一定暗示的。一是Model是整个架构中,代码量最大,复用程度最高, 也是最体现程序员设计功力的地方。 二是View和Controller相对于Model而言,在实际开发中,复用程度不高,逻辑复杂程度较低。 可以说,Model设计得好,整个MVC就好,应用开发起就顺。
因此,这一节将以Model为核心,来讲MVC的设计。 实话说,MVC尽管提出了Model View Controller的划分思想,但到了具体实操中,并不是很好把握的。 下面介绍的设计参考,也仅仅是个人在实际项目中的一些体会和想法,仅作参考。 在具体设计中,可以后把握这么几点:
应用当中涉及到的所有业务对象都应尽可能抽像成Model。 如,博客系统当中,文章要抽象成Post,评论要抽象成Comment。 而相关的业务逻辑,如发布新文章可以用 Post::create() ,删除评论可以用 Comment::delete() 。 这样子整个应用就显得很清晰明了。
在一个应用中,特别是对于大型复杂应用,Model间关系可能比较复杂。在构造应用时,特别是基础Model时, 要从足够小的粒度来设计。 此时,就要考虑采取继承、封装等措施了。 比如,一个博客文章Post,一般包含了若干标签,在页上一般写在作者、日期等Post字段的旁边。 从逻辑上来看,把标签作为Post的一个属性,是说得通的。 但是如果把标签作为一个属性像标题、正文等字段一样依附于Post。那么在有的功能上,实现起来是有难度的。 比如,客户要求,当一个Post含有标签 “yii, model” 时,可以点击 “yii” , 然后系统列出所有具标签中含有 “yii” 的文章。
为了实现这个功能,正确的设计是单独将标签抽象成Tag。这样,Post和Tag是多对多的关系, 即一个Post有多个Tag,一个Tag也对应多个Post。这个多对多关系可以通过一张数据表 tbl_post_tag 来表示。 接下来,为Post增加 Post::getTags() 方法,并通过 tbl_post_tag 表来查询当前Post的所有标签。 同时,为Tag增加 Tag::getPosts() 方法,也通过 tbl_post_tag 表来查询当前Tag对应的文章。 这样,就具备了实现客户要求的新功能的基础。
因此,在Model设计上,要以尽量小的粒度进行设计。一般而言,粒度越小,复用的可能性就越高。
有的读者可能会问了,既然要求粒度尽可能地小,那么,Post是不是也应当再细化,把段落抽象为Model? 是否有这个必要,看客户需求。一般情况确实没有这必要,如果这么做,那是不是再以句子为单位进行抽象? 但如果客户要求这个博客系统的评论是针对段落进行的评论的, 要将评论显示在对应的段落旁边,甚至显示每个段落评论人次等功能。那么就需要把段落抽象成Model了。
从设计流程上,数据库结构设计与Model的设计是紧密相关的。先有数据库结构设计,后有Model设计。 在设计数据库结构的时候,也是在设计Model。 一般而言,最单元、粒度最小的Model就是根据每个数据库表所生成的Model,这往往是个Active Record。
比如标签的问题,在数据库存储过程中,Post和Tag是分开存的,而且这两个表的字段,没有冗余。 tbl_post_tag 表也只记录他们的ID,没有实质内容。
在获取数据渲染视图,向用户展现时,这两个Model及他们的字段,是完全够用,且没有冗余的。
那么,能不能说 Post 和 Tag 这两个Model是够用的呢?显然还不够。
当用户在创建文章、修改文章、审核文章时,需要采用一个表单来显示来收集用户输入。 其中,对于标签的采集,一般是一个长条的文本框,让用户一次性输入多个标签,并以 , 等进行分隔的。
但是,这个文本框没有一个字段与之进行对应。我们也没办法对这个字段的用户输入进行任何的验证、预处理。
因此,Post的功能是不够用的。不够用怎么办?那就加吧。但直接在 Post 里面加个 public $tagString 并不好。 毕竟只是在使用表单时,才会有这个问题,其他场合,这个字段是没用的。
这种情况下,一般使用继承:
1 2 3 4 5 6 |
public class PostForm extends Post { public $tagString; ... ... } |
这样,当控制器发现用户在创建、修改、审核文章时,可以使用 PostForm Model来渲染视图了, 而其他场合则仍使用Post。这样就在需要时,增加了一个 tagString 的字段用于收集用户输入的标签。
在具体设计过程中,由于Model本身就会包含很多代码,因此,要多使用这继承等手段,把代码组织好。
由于Model的代码量比较大,又集中了大量的逻辑,因此,会在一个Model中有大量的方法。仍然以Post为例, 会涉及到创建、审核、发布、回收等流程,相关的方法比较多,在命名上要用心。 可能会涉及到的、名字又比较接近的方法就有:
这里只是一些获取其他Post的方法,命名比较合理,一看就知道意思。 而且全部写成getter的形式,可以使用读取属性的方式进行访问。
不单单是在Model方法的命名上要用心, 在变量名、类名、方法名等的命名上,也要养成习惯,形成规律。 不要图一时之快,胡乱起名。否则,出来混,迟早要还的。
从MVC的起源来讲,是从桌面应用的开发中发展起来的。从本质来讲,这是一种解决问题的思路和办法。 从实践来讲,这是一种久经考验的有效方式。但是如开头我们讲的,Yii更多的是侧重于后端。 对于Web应用而言,包含Yii在内的许多Web开发框架,都是没有办法知道用户的操作,如鼠标、键盘等操作的。 Web应用想要了解用户的操作,只能依靠用户发送Request。 而对于鼠标、键盘等的响应,只能依靠前端技术,如JavaScript等来实现。
再加上这几年来Web浏览器的功能日臻强大。因此,Web应用开发出现了一个新的发展苗头,就是功能从后端往前端转移。
在前端,通过JavaScript捕获用户操作,进行相应处理。 或是发送回后端获取响应后处理,如通过ajax请求后端数据,实现无刷新的局部页面更新,向用户进行反馈; 或直接在前端由浏览器进行处理,如使用backbone.js、Angular.js等前端框架的数据绑定功能等。 这些都使得Web应用表现得越来越像桌面应用。
后端MVC也在为前后端的发展而改变。 Controller的功能更多的变成了识别是ajax请求还是普通请求, 并根据请求的不同采取相应的视图渲染方式。对于普通请求,正常渲染视图,输出HTML。 对于ajax请求,则返回局部渲染视图,输出HTML片段。有的甚至输出XML或者JSON。 唯一在大潮流中,巍然不动的,还是我们的大Model。