深刻理解MVC架构

MVC

MVC是一种设计模式(Design pattern),也就是一种解决问题的方法和思路, 是上世纪80年代提出的,到如今已经很有历史了。 MVC的意义在于指导开发者将数据与表现解耦,提升代码,特别是模型部分代码的复用性。php

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还是主流。ajax

Yii是一个Web框架,从Web开发的分工来说,Yii的开发工做中,承担后端的内容多一些,毕竟主要就是PHP开发。 前端主要是在HTML、JavaScript、CSS上进行开发,而后经过Yii把前端的内容管起来,如经过Assets等。 这一章要讲的MVC,主要是针对后端的。 前端的MVC严格来说不属于Yii的范畴,这里咱们就不做过多介绍。 若是想了解前端的MVC,能够看看Backbone.js Angular.js等前端框架。数据库

MVC的三要素

MVC是模型(Model)、视图(View)、控制器(Controller)3个单词的缩写。 下面咱们从这3个方面来说解MVC中的三个要素。后端

  • Model是指数据模型,是对客观事物的抽象。 如一篇博客文章,咱们可能会以一个Post类来表示,那么,这个Post类就是数据对象。 同时,博客文章还有一些业务逻辑,如发布、回收、评论等,这通常表现为类的方法,这也是model的内容和范畴。 对于Model,主要是数据、业务逻辑和业务规则。相对而言,这是MVC中比较稳定的部分,通常成品后不会改变。 开发初期的最重要任务,主要也是实现Model的部分。这一部分写得好,后面就能够改得少,开发起来就快。
  • View是指视图,也就是呈现给用户的一个界面,是model的具体表现形式,也是收集用户输入的地方。 如你在某个博客上看到的某一篇文章,就是某个Post类的表现形式。 View的目的在于提供与用户交互的界面。换句话说,对于用户而言,只有View是可见的、可操做的。 事实上也是如此,你不会让用户看到Model,更不会让他直接操做Model。 你只会让用户看到你想让他看的内容。 这就是View要作的事,他每每是MVC中变化频繁的部分,也是客户常常要求改来改去的地方。 今天你可能会以一种形式来展现你的博文,明天可能就变成别的表现形式了。
  • Contorller指的是控制器,主要负责与model和view打交道。 换句话说,model和view之间通常不直接打交道,他们老死不相往来。view中不会对model做任何操做, model不会输出任何用于表现的东西,如HTML代码等。这俩甩手不干了,那总得有人来干吧,只能Controller上了。 Contorller用于决定使用哪些Model,对Model执行什么操做,为视图准备哪些数据,是MVC中沟通的桥梁。

对于MVC中三者的划分并无十分明晰的定义和界线。MVC设计模式只是一种指导思想, 让你按照model, view, controller三个方面来描述你的应用,并经过三者的交互,使应用功能得以正常运转。设计模式

其中,View的部分比较明确,就是负责显示嘛。一切与显示界面无关的东西,都不该该出如今View里面。 所以,View中通常不会出现复杂的判断语句,不会出现复杂的运算过程。 对于PHP的Web应用而言,毫无疑问,HTML是View中的主要内容。这是关于View的几个原则:数组

  • 负责显示界面,以HTML为主;
  • 通常没有复杂的判断语句或运算过程,能够有简单的循环语句、格式化语句。 好比,通常博客首页的文章列表,就是一种循环结构;
  • 从不调用Model的写方法。也就是说,View只从Model获取数据,而不直接改写Model,因此咱们说他们老死不相往来。
  • 通常没有任何准备数据的代码,如查询数据库、组合成必定格式的字符串等。 这些通常放在Controller里面,并以变量的形式传给视图。 也就是说,视图里面要用到的数据,都是拿来就能直接用的变量。

对于Model而言,最主要就是保存事物的信息,表征事物的行为和对他能够进行的操做。 好比,Post类必然有一个用于保存博客文章标题的title属性,必然有一个删除的操做,这都是Model的内容。 如下是关于Model的几个原则:浏览器

  • 数据、行为、方法是Model的主要内容;
  • 实际工做中,Model是MVC中代码量最大,逻辑最复杂的地方,由于关于应用的大量的业务逻辑也要在这里面表示。
  • Model所提供的数据都是原始数据。也就是说,不带有任何表现层的代码。 好比,通常不会在输出的数据中添加HTML标签,这是View的工做。 可是Model能够提供有结构的数据,数组结构、队列结构、乃至其余Model等。 这个结构并不是是表现层的格式,而是数据在内存中的表现。
  • 与输出不一样,Model的输入数据,能够是带有表现格式的数据。 如将一篇文章保存到Post里面,内容中必然包含各类HTML标签。 所以,Model通常要对输入数据做过滤、验证和规范化等预处理。 特别是对于须要保存进数据库的,必定要对全部的输入数据做预处理。 这些预处理,有的实际上是业务逻辑。好比只有主编才能够删除文章,这一验证规则既也是业务逻辑,也是权限控制。 而有些预处理,则不属于业务逻辑,好比,文章标题先后的空格应去除。
  • 注意与Controller区分开。Model是处理业务方面的逻辑,Controller只是简单的协调Model和View之间的关系, 只要是与业务有关的,就该放在Model里面。好的设计,应当是胖Model,瘦Controller。

对于Controller,主要是响应用户请求,决定使用什么视图,须要准备什么数据用来显示。 如下是有关Controller的设计原则:前端框架

  • 用于处理用户请求。 所以,对于reqeust的访问代码应该放在Controller里面,好比 $_GET $_POST 等。 但仅限于获取用户请求数据,不该该对数据有任何操做或预处理,这些工做应该交由Models来完成。
  • 调用Models的读方法,获取数据,直接传递给视图,供显示。 当涉及到多个Model时,有关的逻辑应当交给Model来完成。
  • 调用Models的类方法,对Models进行写操做。
  • 调用视图渲染函数等,造成对用户Reqeust的Response。

Model设计参考

在MVC中,Model排第一,是有必定暗示的。一是Model是整个架构中,代码量最大,复用程度最高, 也是最体现程序员设计功力的地方。 二是View和Controller相对于Model而言,在实际开发中,复用程度不高,逻辑复杂程度较低。 能够说,Model设计得好,整个MVC就好,应用开发起就顺。

所以,这一节将以Model为核心,来说MVC的设计。 实话说,MVC尽管提出了Model View Controller的划分思想,但到了具体实操中,并非很好把握的。 下面介绍的设计参考,也仅仅是我的在实际项目中的一些体会和想法,仅做参考。 在具体设计中,能够后把握这么几点:

Model应当集中整个应用的数据和业务逻辑

应用当中涉及到的全部业务对象都应尽量抽像成Model。 如,博客系统当中,文章要抽象成Post,评论要抽象成Comment。 而相关的业务逻辑,如发布新文章能够用 Post::create() ,删除评论能够用 Comment::delete() 。 这样子整个应用就显得很清晰明了。

基础Model应当尽量细化

在一个应用中,特别是对于大型复杂应用,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就是根据每一个数据库表所生成的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的代码量比较大,又集中了大量的逻辑,所以,会在一个Model中有大量的方法。仍然以Post为例, 会涉及到建立、审核、发布、回收等流程,相关的方法比较多,在命名上要用心。 可能会涉及到的、名字又比较接近的方法就有:

  • getPrevPost(),前一篇文章,用于导航
  • getNextPost(),下一篇文章,用于导航
  • getRelatedPosts($n = 10),获取相关的N篇文章,用于相关文章推荐列表
  • getPostsOfAuthor($n = 10),获取同一做者的N篇相关文章,用于做者文章列表
  • getLatestPosts($n = 10),最新的N篇文章,静态方法,用于文章列表或RSS输出
  • getHotestPosts($n = 10),最热门的N篇文章,静态方法,用于热门文章列表
  • getPublishPosts($n = -1),获取已经发布的N篇文章,静态方法,用于文章列表
  • getDraftPosts($n = -1),获取未发布的N篇文章,静态方法,用于做者页面

这里只是一些获取其余Post的方法,命名比较合理,一看就知道意思。 并且所有写成getter的形式,可使用读取属性的方式进行访问。

不仅仅是在Model方法的命名上要用心, 在变量名、类名、方法名等的命名上,也要养成习惯,造成规律。 不要图一时之快,胡乱起名。不然,出来混,早晚要还的。

MVC与先后端的配合

从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。