【原创翻译】认识MVC设计模式:web应用开发的基础(实际编码篇)

原文地址:http://www.larryullman.com/2009/10/15/understanding-mvc-part-3/

 

全系列INDEX 

翻译:shadowmydx 转帖请注明

在这个系列的第一部分,我讨论了MVC设计模式的各个部分以及它们的具体表现。在第二部分,我谈了些web开发框架的共同约定。那两个部分的文章都是这篇文章的基础,是时候讲讲真正的代码了。个人以为,理解MVC这个词是啥意思、理论上了解MVC设计模式的大概流程以及遵守框架的命名规则并不难,但是如果你就这样开始开发一个项目,你将会在实际编码中迅速的迷失你的方向,最后变得一团糟。

 

问题就出在MVC的运作方式和你迄今为止做的工作基本完全相反。如果你是个PHP程序员,正在开发一个列出所有部门(还是那个雇员-部门应用)的页面,你很可能会这么来写这个php程序:

 

 

  1. 新建一个html页面,将header文件包含进去
  2. 连接进数据库
  3. 查询数据库
  4. 确认收到了查询结果
  5. 通过一个循环检索返回的查询结果
  6. 用某种html的方式将查询结果打印出来,比如table或者list
  7. 关闭数据库连接(也可能不关)
  8. 完成整个html页面

 

这就是我们在哪怕如此简单的页面中需要做的东西。即使你使用了包含文件来处理数据库的连接以及引入模板文件来布局HTML,我们仍然需要处理非常多的逻辑。如果你通过在URL中传递部门ID的方式将那些展示部门详细信息的功能做到了另一个页面中,那个页面恐怕也需要增加一些数据确认的步奏。如果你有一个页面同时展示并处理了某个表单,比如增加一个雇员,那还需要你增加非常,非常多的逻辑。并不是说这种开发方式有什么不对的地方,但是这种方式完完全全站在了MVC设计模式的反面。你可以想象MVC式的开发是一个金字塔,模型在最底层,然后到控制器,最后是很小一部分的视图。

 

 


作为编程的一个原则,视图只需要做很少的一点点事就可以了,大多数情况下就打印打印变量的值。视图中最复杂的部分最好也只是通过一个条件判断语句来确认一个变量的值是否存在并打印,或者使用一个循环来打印数组中的全部元素。视图仅仅产生用户能看到的东西,就这么多了,没别的了。控制器中有更多的逻辑,例如应答用户的动作,并且扮演位于模型和视图之间的接口角色。但是这里会有一些常犯的错误,那就是把应该位于模型中的代码放在了控制器中。一个MVC设计模式的原则就是“肥肥的模型,廋廋的控制器”(shadowmydx:原文是fat model,thin controller,大家采那个意思就好)。这意味着你应该把更多的代码压倒应用的地基中去(那就是金字塔的底座 —— 模型)。我曾经看过一篇关于MVC的文章,作者认为模型的功能在于维持http请求的状态。我认为这是个非常棒的描述,因为维持http请求状态就包含了储存数据和处理数据。存储数据,就好比在部门模型中通过不同的请求对数据进行增加、修改和检索,而处理数据就好比在联系模型中获取用户提供的数据,证实后发一个email。(shadowmydx:原文中储存数据为stored data,处理数据为processed data that doesn't necessarily get stored)

 

那么这叫现实的代码中究竟意味着什么?首先需要明确,如果你的视图文件中包含的语句功能超过了echo/print,还有一点控制结构的话,你可能已经在犯错误了。如果你在视图中创建了一个新变量,这也意味着你可能犯错了。实际上,应该只有很少的PHP代码在视图中运行,哪怕你使用循环打印数组也应该选用下面这种形式,把重点放在html上面来。

 

<?php foreach($departments as $n=>$dept): ?>
<li><?php echo $dept->something; ?></li>
<?php endforeach; ?>

与之相反的是我们在通常的PHP程序中采用的方式,重点在于PHP代码:

 

<?php
foreach($departments as $n=>$dept) {
    echo '<li>' . $dept->something . '</li>';
}
?>

 

现在,该讲讲控制器中的代码了。控制器里的代码主要任务是控制行为,例如对功能进行分配以及对用户的输入做出反应。控制器是一些类,拥有完成实际工作的一些方法。这些工作主要包括检索特定的模型(例如从表中获取一个记录),调用一个模型来完成插入、修改或者删除的任务,然后调用一个视图来展示这些结果。模型的任务,就是剩下的全部部分,例如执行某个查询语句,验证数据诸如此类。不过,在框架中,大部分关于模型的代码都已经内置了,所以你可以调用一些模型自己没有定义的方法,例如save或者delete(这些方法是从框架的model类中继承而来的)。

 


让我们回到那个雇员-部门的例子中来。不妨假设现在你想要有一个页面展示出所有部门以及部门领导的名字。在部门模型中,储存着部门领导的雇员ID,那么,控制器就可以通过调用模型来获取全部的部门(下面的是Yii框架的语法):

$dept = Departments::model()->findAll();


$depts变量会传递到相应视图中来打印给用户。这就是Yii使用MVC的基本方式,当然,部门模型并没有实际定义一个findAll()的方法,这是继承过来的。

 

现在你需要找到每个部门临到的名字来让视图显示。为了找到那个人的名字,你需要获得一个同部门模型中储存ID一致的雇员模型。使用Yii的话,代码就是:

$emp = Employees::model()->findByPk($dept->departmentHeadId);

这句代码的意思是,你想要一个主键值和当前部门模型departmentHeadId值一致雇员模型。(shadowmydx:findByPk中,Pk的意思是primary key,也就是主键)

 

一个你绝对不能做的事就是在视图中写类似的查询语句,这将是一场灾难。也许现在的你更倾向于把这些代码放到控制器中,为了达成这个目的,你需要一个循环来检索部门模型,获得deparmentHeadId,然后查询主键与之一致的employee模型,最后将其与相应的部门关联起来。

 

需要明确的是,Yii以及其他大部分框架都支持连接不同的模型。在Yii中,你可以这么做(当然需要你先弄清模型间的关联):

$departments = Departments::model()->with('employees')->findAll();

 

但是,如果你的框架没有这个功能,或者两个模型之间并没有可供联接的关联,你可以增加下面这个方法到部门模型之中:

function getDepartmentHead() {
    $emp = Employees::model()->findByPk($this->departmentHeadId);
    if ($emp) {
        return "$emp->lastName, $emp->firstName";
    } else {
        return 'unknown';
    }
}

 

在定义了这个方法以后,你就不需要在控制器里增加代码了。你可以在视图中用一个foreach循环来完成这个工作:

echo $dept->getDepartmentHead;

 

还有个重要的例子,假如你想用LastName,FirstName这种格式打印雇员的名字,又或者你有更复杂的输出格式,你为此写的方法应该定义在雇员模型中,然后你就可以在任何一个需要使用这个模型的地方调用这个方法,例如什么展示所有雇员、展示某个雇员的具体信息以及展示部门领导等等部分。一切都是为了代码的复用性。

 

好了,终于结束了。有问题可以去原作者的页面留言

posted on 2014-12-08 11:31  shadowmydx'sLab  阅读(786)  评论(0编辑  收藏  举报

导航