1.MVC概述 1.1 什麼是MVC MVC是MODEL_VIEW_CONTROL的縮寫。MODEL_VIEW_CONTROL是軟件設計的典型結構。在這種設計結構下,一個應用被分為三個部分:model,view和controller,每個部分負責不同的功能。model是指應用程序的數據,以及對這些數據的操作;view是指用戶界面;controller負責用戶界面和程序數據之間的同步,也就是完成兩個方向的動作:一、在根據用戶界面(view)的操作完成對程序數據(model)的更新,二、將程序數據(model)的改變及時反應到用戶界面(view)上。 1.2 MVC的優點 使程序結構更加清晰,增強代碼穩定性 在MVC機制下,應用被清晰的分為model,view,controller三個部分,這三個部分分別依次對應了業務邏輯和數據、用戶界面、用戶請求處理和數據同步。我們知道,對於業務邏輯和數據、用戶界面、用戶請求處理和數據同步這三部分功能來講,用戶界面發生變動的可能性最大,控制部分變動次之,而業務邏輯是最穩定的。所以這種模塊功能的劃分有利於在代碼修改過程中選取重點,而不是把具有不同功能的代碼混雜在一起造成混亂。 便於開發小組進行分工 將應用劃分為model,view,controller三個部分,還有利於在項目小組內按照小組成員各自的擅長進行分工,有利於三個部分並行開發、加快項目進度。
2. MVC機制的實現 2.1概述 上一章介紹了MVC的一般概念,這一章將要講述MVC機制在項目中的具體實現。 2.1.1應用的技術 MVC在某個特定應用上的實現是與該應用的具體技術密切相關的。在網站開發方面,Java是比較成熟和典型的,以一個EJB的項目為例,大部分的系統可能采用Html技術,和J2EE(Java 2,Enterprise Edition)的相關技術,包括:HTML,jsp,JavaBean,Enterprise Java Bean,Servlet。這些技術分布於model,view,controller三個模塊之中:view部分用到HTML、JSP和JavaBean技術;controller部分用到Servlet、Stateless Session Bean 、JavaBean 技術;model部分可能用到Entity Bean、Stateless Session Bean、JavaBean技術。 但是,我們在這個項目用的是PHP技術來實現,因此,我們需要作出一些折衷。一方面,要盡量使程序的邏輯實現能夠平滑地遷移到另一種技術實現(如J2EE),但同時,我們也要考慮到PHP這種腳本語言的局限性。PHP在對於面向對象的支持上同JAVA這種OO的語言不是同一檔次,甚至比PERL都要差得很多。不過,幸運的是,我們的運氣還不算太壞,因為PHP裡面的PEAR是一個面向對象的擴展。使用PEAR的規范,我們一方面可以使用現有的一些PEAR模塊,另一方面,可以使我們的程序是能夠按照OO的思想來實現業務邏輯的。因此,我們將廢棄過去那種模塊化,過程化的編程方式,轉變思路,將所有的東西都用類包裝起來。 在開始介紹實際的設計之前,我們不妨看看這個項目的需求情況 2.1.2項目需求 這個項目目的很簡單,就是要做一個網站的發布系統。發布系統應該能夠支持頻道,子頻道,欄目的劃分,特權區的劃分,用戶角色劃分,頻道各種模板的定制,文章發布的流程控制…… 更為詳細的這裡就不再一一敘述了。 2.1.3層次劃分 我們的應用不但可以按MVC機制分為model,view,controller三個層次,同時還應該可以按照實現的技術和邏輯關系劃分為頁面表現模塊,業務控制模塊,事務管理模塊,工具模塊4個模塊;也可以按照具體的業務劃分模塊。下面再介紹一下這兩種劃分方式: 按業務模塊劃分 按實際業務分為用戶管理、文章發布、模板維護、前台顯示,系統管理五個部分。每一個部分都對應著發布系統的一個方面。這樣,model、view、controller的實現組件就分布於五個業務模塊之中。 按實現技術劃分 由於我們的實現技術采用了HTML技術和PHP的相關技術,按照這些技術,我們又把整個應用分為頁面表現層,業務控制層,事務管理層, 工具模塊。這樣MVC的3層結構又細化為上面這4個邏輯層(參見下圖) 2.1.4 典型的一個用戶請求的時序圖: 如果你現在仍然對於我所說的這些劃分感到很空洞和抽象的話,下面我們來看一個序列圖,這個圖展示了在MVC下面,一個request是如何響應和實現從數據的操作到頁面顯示的全過程: 下面分別將分為具體介紹每一個模塊的實現方式和在整個應用中的作用 2.2頁面表現層 這部分的實現是最為簡單的。由模板和使模板實例化的render類來完成。 模板是使用純HTML來編寫,不存在任何的PHP代碼和控制邏輯,確切地說,本層不涉及任何商業邏輯,它的用途就是根據選擇的模板,和提供的實例的數據,生成一個完整的HTML頁面 下圖是表現層的一個時序圖示意: 首先是AsisstantObj,這是Asisstant類的一個實例(關於Asisstant,後面我們會詳細討論),AsistantObj在init方法中,會分析表現層所要用到的2個XML配置文件,template.xml和render.xml,將相應的template 和render信息裝載到系統中,並且會保存起來,在整個session中都可以使用。 其次,由HanlderObj,hanlder類的一個實例,調用Asisstant類的lookupRender來找到某個模板對應的render實例 調用render實例的setTemplate和setRenderData方法,前者是載入並初始化對應的模板文件,後者是設置需要實例化的實際數據 調用render實例的Show方法。在render實例的show方法中,它會調用自身的一個renderIt方法(這個方法在render類裡面是一個虛方法,每一個繼承render的類都要實現自己的renderIt方法,這個方法裡的主要工作就是根據renderdata生成模板的一個實例,具體實現形式參見下面的模板機制),之後,再調用renderObj的show方法將實例化後的頁面顯示再屏幕上面。 2.2.1模板機制 我們使用PEAR中的IT和ITX模塊作為我們的模板引擎,之所以使用它,是因為ITX類是純PEAR類,在功能上和PHPLIB中的TEMPLATE類似,但是使用上要簡單一些。而且ITX有些特性特別適合這種模板的定制,考慮到我們的頁面表現沒有太特殊的地方,使用ITX能夠滿足我們的需求。 關於ITX的具體使用細節,可以參考ITX的源代碼,這裡是它的一個簡單的使用方法說明: new IntegratedTemplateExtention($root = "") 構建函數,$root是裝載模板文件的目錄。注意,這裡可能有個BUG,在使用下面的函數裝載模板文件的時候,如果裝載的文件名中帶有路徑的話,總是無法找到那個文件,所以我建議你設置$root為模板的目錄,然後再使用下面的函數裝載模板。 loadTemplatefile($filename, $removeUnknownVariables = true, $removeEmptyBlocks = true) 從指定的文件中加載模板,$removeUnknownVariables表示是否去掉未使用的變量標識,$removeEmptyBlocks表示是否去掉沒有使用的塊。 setRoot($root) 設置模板的目錄 setTemplate($template, $removeUnknownVariables = true, $removeEmptyBlocks = true) 從指定的字符串中加載模板。如果你打算把模板數據存放在數據庫中(推薦),這個函數是很有用的。 setCurrentBlock($blockname) 設置當前要解析的塊名 setVariable($variable, $value = "") 設置變量標識的值 parseCurrentBlock() 解析當前的塊 toUChBlock($block) 設置指定的塊,使之即使沒有使用,也在輸出中顯示 parse($block = "__global__", $flag_recursion = false) 解析指定的塊 getBlocklist() 得到模板中的塊的列表 blockExists($blockname) 判斷模板中是否存在指定的塊 2.2.2 模板的定義 由於使用了ITX,模板的定義方式很簡單,使用 來定義一個需要實例化的HTML塊,使用{varname}來定義需要實例化的變量就可以了。對於循環,我們無需在頁面上表現,而是放在render類裡控制,我們通過選定一個HTML塊,然後反復實例化這個塊,就可以生成循環的HTML代碼了。下面是一個例子: {name}{age}{sex} 這將生成如下的HTML代碼: panfan0BOY panfan1BOY panfan2BOY 2.2.3 實例化模板 如在上節所看到的,我們通過給render類設置不同的template和不同的實例化數據,將生成不同的頁面。那麼,如何將模板和它所對應的render聯系起來呢?這是個好問題。為了把復雜的問題簡單化,我們沒有考慮使用一個通用的能夠實例化全部的模板和數據的render,因為這樣會造成這個render邏輯非常負責,而是采用許多render,每個render可以實例化一個或者幾個相近的模板。每一個模板都有一個它相對應的render類的id,這些定義是我們預先定義好的。我們使用了一個xml文件來定義應用中用到的全部的模板。這個xml文件名叫:template.xml,它的基本結構是這樣的: