2 min read

MVC 的概念

刚开始,MVC 的概念给我这编程经历不多的人的感觉是,无从下口。

就像面向对象概念。

前些天看到一篇讲面向对象的,说我们本来只是想要香蕉,结果得到的却是一只猩猩拿着香蕉。

我觉得挺有意思,对我这种函数式编程经历远多于面向对象的人来说,面向对象需要经验,不到某个境界,马上就上手,就颇有种造猩猩的感觉。

为什么有 MVC

MVC 的概念也一样。

为什么要把程序分成三个部分?

理由当然有不少,比如 MVC 设计模式(Design Pattern)有利于后期维护,程序灵动性强,等等等等。

但这种话就好像教育小孩子说,你一定要怎样怎样,不然会怎样怎样。他们没有情境,并无法领会这种东西。我为了弄清楚 MVC 的概念,看了大量的相关内容,但结果呢?还是不知所谓。

Wikipedia 上 MVC 的词条配了张图,倒让我有点直观感受(注:图片来自 Wikipedia):

model-view-controller

但也只是到此为止。再看再多的内容,也不会有助于我理解这个概念。我只好说,光看手册,不下水,是永远无法学会游泳的。

于是找了个 PHP 的 MVC 框架操作。PHP 的基于 MVC 设计模式的框架不少,我用的是 CodeIgniter,原因是以前曾经糊里糊涂地用它做过一个站点。

再结合点 Smarty 模板引擎PHP 的 sprintf() 函数,MVC 的概念就渐渐清晰了。

由 sprintf 与 Smarty 说到 MVC

来看个 PHP 中构建查询语句 中所举的例子:

$sql = "SELECT * FROM `SAM` WHERE Reason ='" . $_POST['why'] . "' AND WHERE Time = '". $_POST['when'] . "'";

类似上面的查询语句,如果我们的 PHP 页面是由 PHP 语言混合 HTML 语言进行构造的,可以想像,它很乱,让人写过一次后就不想再动它。

然而,上述查询语句可以通过 sprintf() 函数来改进:

$sql = sprintf("SELECT * FROM `SAM` WHERE Reason = '%s' AND WHERE Time = '%s'",%_POST['why'],%_POST['when'];

我们先构建 SELECT * FROM <code>SAM WHERE Reason = '%s' AND WHERE Time = '%s' 这个字符串,并且用两个 %s 来暂时占位,然后传入两个变量,做相应的替代。

这样做的好处是,我们清楚的知道第一个字符串说明的是什么,并且将变量从中分离,如果要做修改,无论改变量还是改字符串,都是件很方便的事。

同理,Smarty 模板引擎,先构建一个模板文件,比如,index.tpl:

<html>
<head>
<title>{$title}</title>
</head>
<body>

在这个过程中,我们并不管 title 标签对中包含的是什么,我们只要知道,它个变量就行,就像 sprintf 函数里的 %s 那样,占好位置先。

有变量给我们在模板中占好位置后,我们就需要往模板中传入变量的值,那才是真正要展现给最终用户看的内容:

<?php
require_once('Smarty.class.php');
$smarty = new Smarty();
$smarty->assign('title','Hello world!');
$smarty->display('index.tpl');
?>

在这里,Smarty 将变量 $title 的值通过 $smarty->assign('title','Hello world!') 传入。跟 sprintf 函数的思路很接近。

也就是这样,我们完成了一个分离,即 View 与 Controller 的分离,在上面所举的例子中,View 可以理解为 Smarty 模板 index.tpl 或 sprintf 的第一个参数字符串。

在上面的例子中可以看到,我所传入的变量值都是随手拿来的,现成的,并没有通过数据库取得。当这些变量值存在「存取」状态,与数据库需要来往时,我们就又能从上面的 「Controller」 概念中分离出 「Model」 – 即数据哪儿来,怎么来,怎么去。所以,上面所说的 View 与 Controller 的分离 中,Controller 其实是个伪 Controller,我们还可以从中分离出 Model。

以 Smarty 模板来阐释 MVC 概念,则 index.tpl 负责 View,Controller 负责将数据指定给变量,它相当于程序中的粘合剂,Model 则负责数据哪儿来。

MVC 框架的案例

再来看 CodeIgniter 的一段教程,可以加深上面的概念(注:以下代码均来自 CodeIgniter 网站,未作修改),

view 部分

<h2>Create a news item</h2>

<?php echo validation_errors(); ?>

<?php echo form_open('news/create') ?>

    <label for="title">Title</label> 
    <input type="input" name="title" /><br />

    <label for="text">Text</label>
    <textarea name="text"></textarea><br />

    <input type="submit" name="submit" value="Create news item" /> 

</form>

Model 部分

public function set_news()
{
    $this->load->helper('url');

    $slug = url_title($this->input->post('title'), 'dash', TRUE);

    $data = array(
        'title' => $this->input->post('title'),
        'slug' => $slug,
        'text' => $this->input->post('text')
    );

    return $this->db->insert('news', $data);
}

Controller 部分

public function create()
{
    $this->load->helper('form');
    $this->load->library('form_validation');

    $data['title'] = 'Create a news item';

    $this->form_validation->set_rules('title', 'Title', 'required');
    $this->form_validation->set_rules('text', 'text', 'required');

    if ($this->form_validation->run() === FALSE)
    {
        $this->load->view('templates/header', $data);   
        $this->load->view('news/create');
        $this->load->view('templates/footer');

    }
    else
    {
        $this->news_model->set_news();
        $this->load->view('news/success');
    }
}

具体的代码作用且不说,从上面三段代码中,可以看到,View 部分基本只负责页面的展示,它显示一个表单,用于创建新条目;而 Model 部分也只是将新生成的数据插入到数据库;至于 Controller 部分,就负责得比较多:如果表单检测不通过,则调用 View 部分($this->load->view…),返回原页面;如果表单检测通过,则调用 Model 部分($this->news_model->set_news()) 把数据插入到数据库中,并且显示($this->load->view(‘news/success’)) 成功信息。

你可能觉得 Controller 部分做的事有点多,实际上也有人说 MVC 的设计模式中 Controller 部分堆入了太多内容,而建议 MOVE 的设计模式。但那已经是另一个境地。

报告问题 修订

如果你有自建 https 代理的需求,欢迎尝试 Phantom,一键搭建,方便快捷。查看 demo