MVC架构的职责划分原则

22.2k PHP教程 , 10评论

最近负责一个项目,用了 Yii Framework 的 MVC 框架,刚开始自以为结构很稳健。

但是随着对业务逻辑理解的深入,才开始意识到问题的严重。

我错误地理解了 MVC 中的 Controller,想当然地根据以往的经验,把所有的业务逻辑都放在 Controlleraction 中去实现。

于是,每一个 Controller代码都上千行,越来越臃肿

最后,我下定决心重构代码,起源是一个对外开放 API 接口的需求。

按照现在的架构,代码基本无法复用,我需要把很多功能再重复写一遍,这实在是无法接受。

面向对象编程不仅仅是课本上的名词啊!

真正开始实践才发现,要有面向对象意识,有全局观,是多么难得的一件事情。

MVC设计原则

1 到底什么是 MVC

模型-视图-控制器(MVC)是一种设计框架(设计模式)

MVC 的目标将业务逻辑从用户界面的考虑中分离

这样,开发者就可以更容易地改变每一部分而不会影响其他。

在 MVC 中,

  • Model 代表数据和业务规则
  • View 包含了用户界面元素,例如文本,表单等;
  • Controller 则管理模型和视图中的通信

MVC 在各种编程语言中均有实现,例如 J2EE 应用开发中,

View 可能由 jsp 实现;Controller 是一个 servlet,现在一般用 Struts 实现;Model 则是由一个实体 Bean 来实现。

2 我遇到了什么问题

Yii Framework 是一个流行的 PHP 框架,它借鉴了 Ruby on Rails 的 ActiveRecord(AR) 概念。

数据库中的每一个 table 都可以用 AR 类来方便地进行增删改查操作。

它把 AR 当做 Model,并推荐放在一个名为 models 的目录下面。

于是,我在自动生成表对应的 AR 之后,便望文生义想当然地认为已经拥有了 Model 层。

其实,AR只不过是 DAO (数据访问层),并不是 Model 层

我们的业务几乎全放在了 Controller 里:对用户提交上来的表单进行各种逻辑判断,进行计算,实例化 AR 对数据进行存储……

因为一个 Controller 中会有多个 action,每个 action 都有这样的业务处理。

最后,我发现我的 Controller 代码已经超过了 1000 行。

突然有一天,leader 说,我们这个系统要开放 API 给现有的旧系统调用,要给第三方接口。

第三方只是要给定一个参数,本系统给出个结果值而已,这其中的业务处理它是不关心的。

坏就坏在这里,Controller 已经实现了那些业务,但它是接受表单提交的,怎样能够也接受 SOAP 的 xml 文档呢?

Controller 和套套一样,应该越薄越好。

它的职责应该只是接受用户的输入,然后立刻转发给别的类来处理

这样 Controller 只负责提供不同的接口,我们才能算是将业务逻辑分离出去,而分离出去的业务也很容易进行重用。

分离出来的这部分业务由谁来处理呢?答案应该是 Model

3 View的职责

View部分比较明确,就是负责显示。

一切与显示界面无关的东西,都不应该出现在view里面。

因此,View 中一般不应该出现复杂的判断语句,以及复杂的运算过程。

可以有简单的循环语句、格式化语句。比如,博客首页的文字列表就是一种循环。

对于PHP的Web应用而言,HTML是View中的主要内容

View应该从不调用Model的写方法

也就是说,View只从Model中读取数据,但不改写Model。

所以我们说,View和Model是老死不相往来的。

而且,View中不直接访问$_GET$_POST,应该由Controller传递给View。

此外,View一般没有任何准备数据处理的内容,如查询数据库等。

这些一般是放在Controller里面,并以变量的形式传给视图。

也就是说,视图里面要用到的数据,就是一个变量

4 Model的职责

对于Model而言,最主要就是保存和输出信息

比如,Post类必然有一个用于保存博客文章标题的title属性,必然有一个删除的操作,这都是Model的内容。

数据、行为、方法是Model的主要内容

实际工作中,Model是MVC中代码量最大

Model是逻辑最复杂的地方,因为应用的业务逻辑也要在这里表示。

注意将Model与Controller区分开。

Model是处理业务方面的逻辑,Controller只是简单的协调Model和View之间的关系。

只要是与业务有关的,就该放在Model里面。

数据校验、public常量和变量,都应该放在model层,

也就是说,有可能被重复使用的属性或方法,都应该放在model层,一次定义,到处使用。

Model不应该访问request、session以及其他环境数据,这些应该由Controller注入。

好的设计,应该是胖Model,瘦Controller

5 Controller的职责

对于Controller,主要是响应用户请求,决定使用什么视图,需要准备什么数据用来显示

因此,对于request的访问代码,应该放在Controller里面,比如$_GET$_POST等。

Controller应该仅限于获取用户请求数据,不应该对数据有任何操作或预处理,这应该放在 Model 里面。

对于数据的写操作,要调用Model类的方法完成。

对于用户请求的响应,要调用视图渲染。

此外,一般不要有HTML代码等其他表现层的东西,这应该是属于View的内容。

6 启示

Yii Framework 的官方文档中有这么一段:

In a well-designed MVC application, controllers are often very thin, containing probably only a few dozen lines of code; while models are very fat, containing most of the code responsible for representing and manipulating the data.

简言之,Rich Model is Better

 

参考资料:

  1. MVC之患上肥胖症的Controller
  2. MVC的划分原则
  3. 我们丢失了 Model 层
  4. MVC演化史

10 条评论

一元营销 says: 回复

确实不错,这个要实话实说!

路人甲 says: 回复

可以在model和controller中间加一层business层,处理逻辑都放business更好

歪麦 says: 作者

如果项目不大,可以不加业务逻辑层,如果稍微大一点,加一层层级结构会更加清晰,开发起来也快,很多项目也是这么做的

安德鲁修德曼 says: 回复

新人求问, POST数据是应该让controller预处理一下 传给model还是直接让model处理呢?

歪麦 says: 作者

一般是在model里面写数据处理和验证的方法,在controller里面直接调用。

猫猫猫 says: 回复

可以看看这个。model 也是层结构,可以分为三层
https://stackoverflow.com/questions/5863870/how-should-a-model-be-structured-in-mvc

歪麦 says: 作者

嗯,根据需要细分,结构更清晰

旅途· says: 回复

博主,对于(MVC架构的职责划分原则)我一直很困惑,像你说的,比如有一天,负责人说全部改成api访问,我这个controller应该如何设计才能迅速更改,有例子不,我的理解是再加一层,暂叫service层,service层就是负责从model获取数据并处理,然后提供给控制器,以后要是api的话,只需另写控制器层,从service层拿数据

歪麦 says: 作者

model层的类应该是通用的,比如:(new app\models\User)->getById($id);
那么这个getById()方法从任何控制器、或者其他model类访问都是一样的。
如果负责人说从api访问,那就重新写(或者新建)控制器,数据还是从model层按照这个方式读取数据。
当然,如果输出格式完全改变了,那就按照你的方法比较合适,新建一个service层,处理api数据格式,这样也不用管原来的model。

路人乙 says: 回复

很有启发,谢谢

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

昵称 *