在 Ruby on Rails 出现之前, 我并不知道 Web 开发中的 MVC 应该是什么样子. PHPE 有几篇文章倒是讨论过, 其中也有分歧. 直到我学了 RoR, 以及看了 PHP 世界中崛起的几个仿制品, 我才清楚 MVC 在 Web 开发中的样子. 一度我也认为这是极其聪明的办法. 但是最近我又琢磨出一种代替 MVC 的开发方法, 并且认为把 MVC 用在 PHP 开发上其实算不上聪明. 那么, 不论现在天天 MVC 的, 还是不太认识 MVC 的同学们, 我都先来显摆一下, 说说 Web 开发中的 MVC 是怎么一回事.
MVC 最初是从桌面应用开发中提出来的, 最先提出 MVC 的是 Smalltalk, 后来主流的 OO 开发都接受了 MVC 思想. 而 Web 开发中的 MVC 我想应该是从 Java 阵营中来的(这个我不是很确定), 这里我主要说 RoR 和 CakePHP 中的 MVC 模式, 据说它们的 MVC 模式就是从 Java 中偷来. Ruby on Rails 和 CakePHP 都是拿一个 controller 作为程序的入口, 这个 controller 会包含一个或若干个 models, 每一个 model 都是由一个独立的类实例化出来的, 大部分是 ORM 对象, 和小部分并不对应数据表, 而是用来分担一部分 controller 责任的"模型". 每一个 controller 又会有若干的 action, 每个 action 是一个函数, 分别对应一个 view, 也就是"模板". 程序的每一次执行会化为一个 url, 比如 index.php?controller=home&action=index, 这代表程序将实例化 HomeController 并执行其中的函数 index, 然后包含一个"模板"文件 index, 函数 index 则会把数据变量 set 到模板里面去, 于是我们就看到一个页面. 这一切看上去真是完美无缺, 一个 OO 狂热者必定会为它击节叫好.
但是在 Web 开发中, 特别是在 PHP-Web 开发中, MVC 真的有那么好吗?
我上面提到过, MVC 是由 Smalltalk 阵营提出, 在其它 OO 语言特别是 Java 阵营中遍地开花的. 然而 Smalltalk 和 Java 都是面向对象语言, 而 PHP 本质上是过程式语言, 过程式语言可以比面向对象式语言更加简练.
从一个 action 中把变量 set 进 view 中, MVC 模式是这么传递变量的. 当然 PHP 完全可以这么干, 但是可以更简单, 我的答案您可能会嗤之以鼻, 我的方法就是----全局变量. 从一个 OO 狂热者的角度来看, 全局变量是愚不可及的, 它总是四处破坏封装. 但是 PHP 是一种过程式的语言, 换一种角度来看, 只要代码长度总是可以控制, 全局变量和全局函数铁定比 OO 和 MVC 要省事得多. 基于"代码长度决定项目的开发和维护成本"的原则, 我决定寻找一种能代替 MVC 的模式. 下面阐述我的模式----OO 与 PO 混合仿 MVC 又看不出 MVC 的新模式.
Web 程序总是由 url 触发的, 世界上没有一只鼠标可以同时点击两个链接, 因此对同一个用户来说, Web 程序没有并发进程. 换句话说, MVC 中的同一个 controller 中的不同 action, 它们表现为两个不同的 url, 它们的执行过程是互不相干的.
假设 Web 程序的通用入口是 index.php, 我从这里开始介绍我的模式.
CODE |
|
这是我的 index.php 中的主要部分, 其它一些琐碎代码都是围绕这个中心进行的. 我定义了 2 个重要变量 $app 和 $action, 默认值是 home 和 index. 如果用户访问网站的根目录, 就执行 ./apps/home/home_entrance.php, 接着运行 ./apps/home/index.php. 在这里 ./apps/home 整个目录代表传统 MVC 模式的整个 controller 及其所有的 views 的集合, 但是没有类, 只有一个个的子过程, 这就是"OO 与 PO 混合仿 MVC 又看不出 MVC 模式"的主要构成. home_entrance.php 是整个网站的首页入口, 比较复杂, 下面跳到另一个 url 来看看.
假设用户点击 index.php?app=admin, 进入管理页面需要登录, 我在 ./apps/admin/admin_entrance.php 中写:
CODE |
|
这段代码等于传统 MVC 模式中 AdminController 类中的构造器, 也可以是 Rails 中的一个 before_filter, 不同之处是代码少了很多. 它的作用是把关, 影响全局变量 $action, 把不合法的访问导向 login 页面. 我也可以加入一句 $layout = 'admin', 这样页面输出也会被导向另一种风格.
下面看看 ./apps/admin/login.php
CODE |
|
熟悉 RoR 和 Cake 的同学对代码中的两个函数 flash() 和 redirect_to() 必定不会陌生, 它们是贯穿整个项目的全局函数(我看不出有让它们从属于某个类的必要). $user = $User->new() 这一句是我的独门秘技 QiQ 做出来的一套 ORM, 用其它的 ORM, 你可以写 $user = new User, 而 $User->select() 可以是 User::select(). 比较触动某些人的神经的可能是我的 PHP/HTML 混写风格. 是的, 我是混写了, 但是我把传统 MVC 模式的 action 和对应的 view 放在一块儿, 并非混合了业务逻辑和显示逻辑, 在这里我完全看不出分离 action 和 view 的必要. 函数 login() 和 authorize_user() 是扎扎实实的子过程, 并非实现某种功能的公共库, 它们的存在仅仅是为变量提供一层命名保护.
到这里已经可以暂时打住, 相信大家完全可以看出我的意图: 我把传统上非常复杂的 MVC 完完全全给过程化了. 过程式语言并非一无是处, 根据一个简单的约定, 我就完成了 MVC 模式非常复杂的实现. Java 们看见这样简单的实现, 肯定要眼红, 谁叫 Java 没有全局变量和全局函数呢?
但是全局变量和全局函数肯定也有短处, 所以要有一些约束. 我们可以统计一下 index.php 中出现的全局变量, 计有 $app, $action, $output_content, 和 $layout, 全局函数有 flash(), flash_out() 和 redirect_to(), 这些在 action 文件中不可再用, 这与类属性和方法不可重名是一样的.
如果把一个 app 目录看作一个大的 controller 的话, 那么甚至可以把 index.php 看作这个 controller 的一部分. 这样一来就非常清晰了, 我确实在进行 MVC, 但是你看不见 controller 类, 看不见 view 文件, 它们被我完全过程化了.
总结, "OO 与 PO 混合仿 MVC 又看不出 MVC" 模式的主要优点是:
1. 代码量大幅降低, 系统复杂度大幅降低, 间接地成本大幅降低
2. action 直接和 view 结合, 清晰度大幅上升
3. 把 "controller" 拆散成许多子过程, 有利于构建大型项目.
国内网站习惯把大量内容放在首页上, 如果用传统的 MVC 模式来构建首页, 由于所有 actions 集中于一个 controller 中, 导致 controller 过度复杂(可能有数十个 action). 分拆之后每个程序员可以负责若干 action, 更有利于协同开发.
4. 如果实在需要分离 view, 可以在 index.php 中增加一句
CODE |
|
这样就可以把 view 分离出来交给美工人员去做.
以上就是我今天的发言.
===================================================================================
在我的眼里,对PHP来说,MVC的最大好处一是分离php代码和html代码(换种时髦的说法是分离出页面/美工),另外就是把php代码又“提炼”出一部分所谓的“业务逻辑”来作为类封装。
这么说你并不理解真正的 MVC, 请问 M 在哪里? 以我的 ./apps/admin/login.php 为例, 在 CakePHP 中, 它会是这样的:
CODE |
|
在 Ruby on Rails 中可以是(实际上不是这样, RoR 的所有 controller 继承自 ApplicationController):
CODE |
|
===================================================================================
任何架构,都要简单易学,容易上手才行,不然说什么架构优良都是空的。凭空设计一种模式出来,培训开发人员要花多少时间呢,你不可能都一个人作项目吧。。 MVC还是比较简洁的。
我喜欢写代码可以一块一块地加,而不是一行一行地写,代码间的依赖性越小越好,分层我觉得是必需的,要怎么分当然大家各有看法。 用全局变量,大大增加了代码间的耦合度,如果是别人来改你的代码就会觉得吃力了。自己写得爽,也要别人用得爽才行呀。用OO并不是对用户来说有什么优势,而是对于Client Coder很有好处。这对于大项目中的协作非常重要。
==================================================================================
我的想法并不是空穴来风, 我可以向大家交待一下最近的思想动态. 第一, 我最近学 Perl, 脑子里充满过程化编程的思想; 第二, 前段时间我鼓捣了一个东西叫 QiQ, 它可以用 yaml 定义来生成对象, 为了让它早点进入实际生产, 我拿它仿着 Active Record 做了一个 ORM; 第三, 为了测试这个 ORM, 我写了一个 blog 程序, 最初的 controller 也是用 QiQ 来生成, 但是我在定义 before filter 和 after filter 时出了错, 为了测试我就干脆放开了纯粹过程化地写, 写着写着我就发现原来这样的写法比辛辛苦苦去构建 controller 爽多了, 代码也少很多, 所以我放得更开, 把原先的 OO 思想抛回爪哇国去, 搞成 action 和 view 直接绑定, 加一点小小的约定, 除了全局变量丑陋一点, 效果和 OO 没有两样.
顺致 haohappy, 如果你把我的每一个 app 看作一个 controller, 你就会发现每一个全局变量都是 controller 的类内属性, 每一个全局函数都是从 controller 祖类继承而来的方法, 耦合问题是有一点, 但是绝对不到"大大增加"的程度. 满脑子 OO 思想的人对过程化编程的看法我清楚, 但是你写 Java 的时候, 会不会有时候也想说"如果这个地方可以过程化一点, 那我就可以少走很多弯路"呢? 还有一点, 你难道看不出我是在尝试用 OO 思想去写过程化的程序? 把一个大页面切割成若干 iframe, 每个 iframe 就是一个 action, 每个 action 由专人负责, 管理上也可以很方便, 对程序员的要求也降低了(设计对白: 你会 PHP 是吧? 写个函数看看, 就写 Hello World. 写好了是吧? 我看看, 嗯不错, 的确是 Hello World! 好, 你被录取了, 明天上班)
==================================================================================
我不懂MVC。
我个人看法:
把整个过程分为:原始数据+格式化数据+显示格式
原始数据一般就是保存在数据库中的。
格式化数据也就是显示前的数据。
显示格式就是页面中比较固定的东西。
显示分两种:传统(跳转)+ajax
无论哪种显示都是
CODE |
|
$arrData为格式化数据
$fmStr为窗口显示
$iViewMode显示模式
$fmStr中一般包含两种,固定的和非固定的
固定的直接采用str来描述。我采用dw,,然后copy它到str.(因为我用ajax,如果传统,那么更加舒服了)
非固定的,比如FormId,FormAction,FormClick等,采用_gtFormSettings获得
做项目:通过switch或者其他方法将整个流程拆解到:原始数据+格式化数据+显示格式
我们就可以腾出更多时间来关注格式化数据,也可以很方便的分出来大家一起做。
==================================================================================
想美观点很容易,构造一个controller类,用单件模式,设置一个静态数据成员(跟全局变量的意义一样),一个公共方法来接住action变量。
起码可以防止全局的$acition变量被随便覆盖掉,又可美观了点,但认真想想又很多余,这个类实例的句柄不也是需要一个全局变量来承接吗?
很多所谓PHP里的OOP都是这么做的。虽然我对用一个过程语言来模仿OOP很抵抗,所以楼主这种“传统”的过程思想很赞赏,什么样的语言就该走它自己的路。但似乎PHP的趋势是向JAVA靠拢,就像16/32位混合体的WIN98逐渐过渡到纯32位的WIN2K(XP)等,那这样下去,到最后PHP就只能说是改了名字的JAVA而已。
==================================================================================
3. 把 "controller" 拆散成许多子过程, 有利于构建大型项目.
这到不见得,拆开了,每个function都global xx,你写不累,我的审美观也累。拆开了分开到各个$action.php,打开那个文件夹,也够呛的。是不是if.else变多了?
OOP嘛,PHP的class显然不能构成严格的oo,尽管我可以把它批到体无完肤,但是写起来,还是能很爽的,只要看清楚了OO的 各个属性/特性/XXXX 经过各个编译器之后其实都是过程.C++就是通过C++编译成c的目标代码,然后再调用c的连接器生成程序的。JAVA,C#都是换汤不换药,只是能实现的细节就越来越少了。try catch很OO把,不过我记得汇编/c可以jump,goto,变种而已。
再看清楚点,每个原文件都是一个class,可能很少人能理解这点。所以呢,没有所谓的c++的OO,java的XX,然后php的ooxx。明白了运行机制,哪种语言都能出来OO.
写class不是写class,在写想法而已。至于把function写到class里,可以更好的把握全局。反而global却不能很好的全局。
lz只是认为拆开function这样include进来,解析速度就快了,一种效率的意淫而已嘛。人能接受的速度是0.03秒。把全部function包含进来都不会超过这个时间的.最后还是会发现西瓜确实比芝麻大。
===================================================================================
请问你看清楚了吗? 我是说当你 OO 的时候, controller 就是一个类, 只能由一个人去开发维护, 当你碰到一个超级复杂的 controller, 含几十个乃至上百个 action, 就不如拆散由多人共同开发和维护. 真不知您哪儿来那么多废话?
===================================================================================
我觉得,唉,我很少去考虑这些东西了,我的代码基本都是这样:
CODE |
|
感觉很轻松自在,Hoho~~何必整在一起呢?
models 是一些相对独立和抽象的东西, 用户只能接触到 controller 里面的 action, 我是体会到 PHP 完全可以不这么辛苦相反却可以用方便得多的办法达到同样效果才发的这篇文章, 只有先吃透 MVC (具体地说是 MVC 的 Web 应用)才会知道我在干什么. 如果你知道 MVC, 你就知道我其实就是在 MVC, 只不过我把它给过程化了, 它也许很丑陋, 但是它带来了开发效率的提升.
===================================================================================
可能如楼主所说,我确实对MVC不太理解。
我所理解的,M就是“业务逻辑”,比方说,模板机制或者说模板类就是个业务逻辑,就是M。它是一个相对独立并且能够抽象的东西,不过模板类比如smarty是个特殊的业务逻辑,它来实现V和C的分离。另外数据库类比如ADODB也是M,它负责和数据库交互。
因为我的理解在应用中很有效,所以我一直没有怀疑。"再"所以粗略看了一下你的文章,从我的理解的话,你还是在搞MVC,只不过换了一些概念,改变了一些程序的组织和表现形式。
我感觉php的精妙就在于既能够过程化又能OO化!
过程化的东西我们继续,OO化的东西我们也能够吸取其精华。。。
我这么认为:
目前来说,M是的东西最好是OO化的,而结构化的东西充当C,还没有什么很大的弊病!
===================================================================================
To hick: 我想你并不清楚什么是 MVC, 而你所谓的"业务逻辑与显示逻辑分离"则和 MVC 根本不沾边. 这么办吧, 你先来这里下载一个 CakePHP 框架, 这是目前最易用的 MVC 框架, 然后花半个钟头把这个教程照做一遍, 嫌麻烦的话照抄然后慢慢去琢磨代码也可以, 这样你会对 MVC 开发有些大概的了解. 你再总是这么随意地猜测我真的很难跟你沟通哎.
==================================================================================
个人觉得有点问题:
1.如果不同的用户有不同的视图,是否每个都做 action + view,如果不是,那么划分的规则又是什么?
2.如果讨论的是把controller由集中式向分布式过渡,我觉得会比较自然而且合理,但这里看上去还是集中到index controller,那么实际上的作用是再三寻址找脚本的过程,看不出太大的好处:0;
3."action 直接和 view 结合, 清晰度大幅上升"吗?可能对程序员是,但意味着以后的维护也需要你的参与;
4.有句话不解,“因此对同一个用户来说, Web 程序没有并发进程.”
==================================================================================
to bennybi:
1. "不同的用户有不同的视图"这句话的意思是..?
2. "把controller由集中式向分布式过渡"这句话好. 我写的快, 脑子一时没转过弯来, 没总结这句话出来, 谢谢. 但是集中到index controller的意思是..? 我好像没出现什么index controller.
3. view 是可以有多种形式的, 它不是仅仅指 html, 在我看来, view 是输出数据的过程, 而不是泛指各种显示形式. 对程序员来说, 输出数据一个 print_r() 就足够了, 插入数据我们可以做 unit test. 而 view 的表现形式可以是 html, xml, yaml, 甚至是 javascript 数组. 如果你想做换肤功能, 我建议你做 AJAX+CSS. view 是 MVC 的重要构成部分, view 不是显示形式, 它是指把服务器端的计算结果输出到客户端屏幕上的过程, 它绝对是应该由程序员操纵的. 而象我这样的 html 专家, 直接输出 html 就可以. 逊一点的, 象我上面所说的, 动一点点脑筋就可以象传统 MVC 开发一样把"模板"统一放到另一个目录中. 而我一直认为, 被过分强调的"业务逻辑与显示逻辑完全分离"只是一些完全不懂 html 的不合格程序员偷懒或逃避的托词.
4. “因此对同一个用户来说, Web 程序没有并发进程.”这句话又是我写得太快的笔误, 措辞上有点问题. 可以这么理解: 用户永远只能同时提交一个 url, 他只能一次访问 controller 中的一个 action, 所以当你在程序中使用全局变量时, 它不会影响到其它的 action, 只管大胆地用好了. 我这么说是想和桌面开发的 MVC 区分开来, 因为桌面程序中如果用户不关闭窗口, controller 就不死, 一个全局变量就会影响到全部的 action. 而 WEB 开发中用户每一次请求都是一个新的开始, 这给了我使用全局变量的机会.
===================================================================================
说实话,感觉PHP高效 简洁不是因为它可以面向过程,而是因为它是动态脚本语言。
而PHP自有自己的开发模式,所以,我更希望PHPer能像楼主这样,有自己的pattern,PHP Pattern。
但是,但是提出一些自己的疑义。
首先。我认为楼主的思路不错,但是是否是MVC模式还有待商榷。开始说像MVC挺好的,可是后来lz自己说的其实已经在MVC了,这个我不太同意。M为model C为controller V为view,此Model可以理解为业务上抽象的实体,也可以理解为业务上抽象出来的服务。这里面和重要的就是controller在web中的mvc要掌控全局。因为MVC模式提出的本意,model更新要由view获得,而web中服务器不会主动的推数据给view(php)来更新视图,所以需要观察者模式予以辅助。而作为控制中心的controller,他要掌控view与model的关系,这样项目大起来了才可以达到可配置化 结构可视化。此处lz的一个请求到达controller以后,就被委派到了一个php文件,而此php文件很可能是另一个view(当然,这也是合理的)也可能是一个model,但是Model执行了相应操作以后却要自己负责切换视图,那么如果很大的一个网站,lz走了以后,没有充分的文档,后来者除了看代码有办法知道view与model的关系么?这个地方我觉得是值得商榷的。
Cake不是很熟悉,离开PHP开发好久了,不过还是很喜欢PHP,就因为它的随心所欲。
to hick:
PHPer对MVC的理解最容易陷入模版等一类的概念中,记得好久以前easy纠正过,没有模版也一样可以MVC 模版还是php都是指view的一种表现方法罢了。
啥话呀, 怪我了是不是? 那我道歉
MVC 没啥大不了的, 它主要是一种方法学, 教人如何合理安排组合代码, 其中技术性的东西挺少的, 你找份真正 MVC 的代码看一看总比干坐着臆猜要好. 我发这篇文章是因为我在做 MVC 开发中发现其实可以用另一种方便得多的方法来 MVC, 而且这是我一个人的方法, 并不想说服任何人. 而标题中的 "MVC" 指的是传统 MVC, 也许标题可以叫做 < 超越 MVC >.
==================================================================================
QUOTE (blueoxygen @ 2006-02-27 12:13) |
我认为你所说的三层仅仅是物理上的三层:浏览器 appServer DB 并非逻辑上的三层。比如你的那段小程序,表现层可以说在模版里。可是逻辑业务层和DB access layer严重耦合。他们并没有区分成为两层。仅仅用了一个DB操作的类库而已。 虽然不知道$record具体结构,直接assign给模版引擎解析,但是我可以推断这也是一种耦合。如果模版引擎更换了,$record能被正常的解析么?如果DB类被更换了,模版能被正常的解析么?如果更换了数据库,SQL语法有了变化了,是不是需要更改DB类和此示例类?如果逻辑发生了变化呢? 分三层或者N层根本目的是为了解耦。 |
to blueoxygen,
你说的其实就是model和view是否要避免耦合. 使用任意一种所谓的template engine的时候. 很多人controller的代码就与这样的template engine耦合了. 这样的设计不能不说是土得掉渣. 而很多人自以为是的model其实根本就不是model.
我曾在帖子里提过, 如webwork的设计. 每种view都仅是一种实现罢了, ioc, 随时需要随时更换.
有很多人总是习惯于并喜欢机械的assign. 人各有所好吧. 不必强求.
to cid,
仔细思考一下blueoxygen的问题, 然后给出个想法如何?
==================================================================================
发完才看到楼主要作结了,对不起了
BTW:LZ的代码结构设计应该是很早以前就有人用过的,好象phpE这个网站的代码就和你的结构很接近的说,这种方法应该说是可以用,有优点,但方便维护和多人合作决不是它的优点,缺点我想大家应该也很清楚,就不多说了.
楼主和结构和深空的根本区别再于1)没用class封装 2)用的是直接模版或者说是没有分离模版 其它的都是小区别
对于MVC我一直都没大搞清根本概念,相关的书我也没看(我这个人不喜欢看书,喜欢看手册和帮助文档),倒底标准的MVC是个什么样子我现在越来越糊涂.我是把他做为一个思路参考一下,实际用的时候还要看怎么好用怎么来,感觉如果真的为了标准而标准,实在是找累