深入理解MVC架构
MVC
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的三要素
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  | 
public class PostForm extends Post  | 
这样,当控制器发现用户在创建、修改、审核文章时,可以使用 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。
深入理解MVC架构的更多相关文章
- Java ---理解MVC架构
		
之间的文章,我们主要是介绍了jsp的相关语法操作,我们可以通过请求某个jsp页面,然后由相对应的servlet实例给我们返回html页面.但是在实际的项目中,我们很少会直接的请求某个页面,一般都是请求 ...
 - [概念理解] MVC模式和C++的实现
		
[转]学习可以是一件很快乐的事,特别是当你发现以前所学的点点滴滴慢慢地能够串起来或者变成了一个环,这种感觉真好.这篇文章就这么来的. 从MVC架构开始说起吧.这两天系统了解了一下MVC架构的内容,主要 ...
 - MVC架构中的Repository模式  个人理解
		
关于MVC架构中的Repository模式 个人理解:Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间.它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接 ...
 - 初学者对于MVC架构模式学习与理解
		
理解MVC的工作原理,明白一个网页是如何显示出来的 之前一直盲目的在慕课上看视频,脑袋里想着要理解mvc,看了mvc相关的视频,看完之后就觉得空白白的,M,V,C各代表什么我知道,但是这个究竟有啥意思 ...
 - 一个初学者对于MVC架构的理解
		
我很早之前就开始接触.NET开发,一直都在2.0的框架下,所以对于MVC这种架构,听说过,但没有具体使用过,近期和外部朋友接触时,有了解到他们公司在使用MVC这种架构,所以自己就找来相关资料了解一下M ...
 - java MVC架构-spring mvc,struct2(理解)
		
MVC架构实现基础: 基于filter或者servlet实现请求地址分析,如果需要控制类处理请求,则调用相应的控制类.调用控制类时,根据配置文件初始化控制类相关的参数.数据库连接可持久化存在.控制类处 ...
 - 【JAVA】基于MVC架构Java技术荟萃案例演练
		
基于JAVA-MVC技术的顾客管理项目案例总结 作者 白宁超 2016年6月9日22:47:08 阅读前瞻:本文源于对javaweb相关技术和资料汇总,涉及大量javaweb基础技术诸如:Servle ...
 - 通过Ruby On Rails 框架来更好的理解MVC框架
		
通过Ruby On Rails 框架来更好的理解MVC框架 1.背景 因为我在学习软件工程课程的时候,对于 MVC 框架理解不太深入,只是在理论层面上掌握,但是不知道如何在开发中使用 MVC ...
 - 深入理解MVC与MVP
		
http://www.cnblogs.com/seaky/archive/2011/04/06/1982533.html 在深入分析MVC和MVP之前,我们有必要回顾下经典的三层架构.分层是计算机学科 ...
 
随机推荐
- Blocking & Nonblocking module
			
/*************************************************** / Blocking and Nonblocking circuit and Simulat ...
 - 关于top按钮的网页设置
			
方法一: 直接用html实现,没有缓冲的效果,直接彪到顶部. HTMl: <a id="return-top" href="#top"> <s ...
 - java - day12 - InteraceTest
			
接口的实现.继承等 package test.interfacedemo; import test.interfacedemo.Inter; public class InterfaceDemo { ...
 - python 循环结构
			
for循环 list或tuple可以表示一个有序集合.如果我们想依次访问一个list中的每一个元素呢?比如 list: L = ['Adam', 'Lisa', 'Bart'] print L[0] ...
 - 用@spy模拟真实对象的部分行为
			
1.说明在某些情况下,我们需要使用一个真实对象.但是,我们同时需要自定义该对象的部分行 为,此时用@spy 就可以帮我们达到这个目的. 2.用法: categoryService = PowerMoc ...
 - iOS 如何缩小打包项目ipa大小
			
之前项目上线完全由技术老大搞,这次独立开发自己来,觉得自己的打包项目体积略大,网上搜索了一些比较不错的方法,这里总结下 1.配置编译选项 (Levels选项内)Genetate Debug Symbo ...
 - [转]成员函数指针与高性能的C++委托
			
原文(作者:Don Clugston):Member Function Pointers and the Fastest Possible C++ Delegates 译文(作者:周翔): 成员函数指 ...
 - Yarn源码分析之事件异步分发器AsyncDispatcher
			
AsyncDispatcher是Yarn中事件异步分发器,它是ResourceManager中的一个基于阻塞队列的分发或者调度事件的组件,其在一个特定的单线程中分派事件,交给AsyncDispatch ...
 - TPM概述
			
TPM(Trusted Platform Module)安全芯片,是指符合TPM(可信赖平台模块)标准的安全芯片.标准由TCG(可信赖计算组织,Trusted Computing Group)提出,目 ...
 - Unity3D学习笔记——NGUI使用常见问题
			
一:在Scene中右键创建UI组件. 首先要确定NGUI中的红框能看见,我的就是之前无法现实红框,所以右键无法新建UI组件, 原因是:UIPanel没启用. 启用方法: 启用前和启用后NGUI界面对比 ...