MVC--深入理解YII2

MVC

MVC是一種設計模式(Design pattern),也就是一種解決問題的方法和思路, 是上世紀80年代提出的,到現在已經頗有歷史了。 MVC的意義在於指導開發者將數據與表現解耦,提高代碼,特別是模型部分代碼的複用性。

MVC不僅僅存在於Web設計中,在桌面程序開發中也是一種常見的方法。MVC的出現已經有一段歷史了。 記得我最早瞭解到MVC的時候,是在Microsoft的Visual C++ 中的MFC中。 當時年少無知,以爲是MFC中特有的東西。後來隨之不斷學習,才發現自己的天真。 所以說,學得越多,就越覺得自己無知。越覺得自己無知,就越懂得敬畏和謙遜。 從這個角度講,同學們,最好不要看不起謙遜的人。

有個這麼一個段子,說一天A君在圈內聚會時,朋友介紹了另一個人B君互相認識。 聚會場合嘛,這很正常,也很普遍。於是AB君小聊了一下。按國人的習慣,A君就問了“先生在哪高就?”。 B君只說了句,“談不上高就,炒炒股。” “哦,原來是炒股的。”A君心想,雖沒覺得什麼不對,但心理覺得B有點low,只是沒說破,也沒表現出來。 過後了一段時間,一次偶然機會,發現原來B君是國內某上市公司的二股東,身家過億。 人家沒說慌,確實是炒股的……

話說遠了,我們還說正題。MVC是三個單詞的縮寫:Model, View, Controller。 MVC是一種設計模式,目前幾乎所有的Web開發框架都建立在MVC模式之上。 當然,最近幾年也出現了一些諸如MVP, MVVM之類的新的設計模式。 但從技術的成熟程度和使用的廣泛程度來講,MVC仍是主流。

Yii是一個Web框架,從Web開發的分工來講,Yii的開發工作中,承擔後端的內容多一些,畢竟主要就是PHP開發。 前端主要是在HTML、JavaScript、CSS上進行開發,然後通過Yii把前端的內容管起來,如通過Assets等。 這一章要講的MVC,主要是針對後端的。 前端的MVC嚴格來講不屬於Yii的範疇,這裏我們就不作過多介紹。 如果想了解前端的MVC,可以看看Backbone.js Angular.js等前端框架。

MVC的三要素

MVC是模型(Model)、視圖(View)、控制器(Controller)3個單詞的縮寫。 下面我們從這3個方面來講解MVC中的三個要素。

  • Model是指數據模型,是對客觀事物的抽象。 如一篇博客文章,我們可能會以一個Post類來表示,那麼,這個Post類就是數據對象。 同時,博客文章還有一些業務邏輯,如發佈、回收、評論等,這一般表現爲類的方法,這也是model的內容和範疇。 對於Model,主要是數據、業務邏輯和業務規則。相對而言,這是MVC中比較穩定的部分,一般成品後不會改變。 開發初期的最重要任務,主要也是實現Model的部分。這一部分寫得好,後面就可以改得少,開發起來就快。
  • View是指視圖,也就是呈現給用戶的一個界面,是model的具體表現形式,也是收集用戶輸入的地方。 如你在某個博客上看到的某一篇文章,就是某個Post類的表現形式。 View的目的在於提供與用戶交互的界面。換句話說,對於用戶而言,只有View是可見的、可操作的。 事實上也是如此,你不會讓用戶看到Model,更不會讓他直接操作Model。 你只會讓用戶看到你想讓他看的內容。 這就是View要做的事,他往往是MVC中變化頻繁的部分,也是客戶經常要求改來改去的地方。 今天你可能會以一種形式來展示你的博文,明天可能就變成別的表現形式了。
  • Contorller指的是控制器,主要負責與model和view打交道。 換句話說,model和view之間一般不直接打交道,他們老死不相往來。view中不會對model作任何操作, model不會輸出任何用於表現的東西,如HTML代碼等。這倆甩手不幹了,那總得有人來幹吧,只能Controller上了。 Contorller用於決定使用哪些Model,對Model執行什麼操作,爲視圖準備哪些數據,是MVC中溝通的橋樑。

對於MVC中三者的劃分並沒有十分明晰的定義和界線。MVC設計模式只是一種指導思想, 讓你按照model, view, controller三個方面來描述你的應用,並通過三者的交互,使應用功能得以正常運轉。

其中,View的部分比較明確,就是負責顯示嘛。一切與顯示界面無關的東西,都不應該出現在View裏面。 因此,View中一般不會出現複雜的判斷語句,不會出現複雜的運算過程。 對於PHP的Web應用而言,毫無疑問,HTML是View中的主要內容。這是關於View的幾個原則:

  • 負責顯示界面,以HTML爲主;
  • 一般沒有複雜的判斷語句或運算過程,可以有簡單的循環語句、格式化語句。 比如,一般博客首頁的文章列表,就是一種循環結構;
  • 從不調用Model的寫方法。也就是說,View只從Model獲取數據,而不直接改寫Model,所以我們說他們老死不相往來。
  • 一般沒有任何準備數據的代碼,如查詢數據庫、組合成一定格式的字符串等。 這些一般放在Controller裏面,並以變量的形式傳給視圖。 也就是說,視圖裏面要用到的數據,都是拿來就能直接用的變量。

對於Model而言,最主要就是保存事物的信息,表徵事物的行爲和對他可以進行的操作。 比如,Post類必然有一個用於保存博客文章標題的title屬性,必然有一個刪除的操作,這都是Model的內容。 以下是關於Model的幾個原則:

  • 數據、行爲、方法是Model的主要內容;
  • 實際工作中,Model是MVC中代碼量最大,邏輯最複雜的地方,因爲關於應用的大量的業務邏輯也要在這裏面表示。
  • Model所提供的數據都是原始數據。也就是說,不帶有任何表現層的代碼。 比如,一般不會在輸出的數據中添加HTML標籤,這是View的工作。 但是Model可以提供有結構的數據,數組結構、隊列結構、乃至其他Model等。 這個結構並非是表現層的格式,而是數據在內存中的表現。
  • 與輸出不同,Model的輸入數據,可以是帶有表現格式的數據。 如將一篇文章保存到Post裏面,內容中必然包含各種HTML標籤。 因此,Model一般要對輸入數據作過濾、驗證和規範化等預處理。 特別是對於需要保存進數據庫的,一定要對所有的輸入數據作預處理。 這些預處理,有的其實是業務邏輯。比如只有主編纔可以刪除文章,這一驗證規則既也是業務邏輯,也是權限控制。 而有些預處理,則不屬於業務邏輯,比如,文章標題前後的空格應去除。
  • 注意與Controller區分開。Model是處理業務方面的邏輯,Controller只是簡單的協調Model和View之間的關係, 只要是與業務有關的,就該放在Model裏面。好的設計,應當是胖Model,瘦Controller。

對於Controller,主要是響應用戶請求,決定使用什麼視圖,需要準備什麼數據用來顯示。 以下是有關Controller的設計原則:

  • 用於處理用戶請求。 因此,對於reqeust的訪問代碼應該放在Controller裏面,比如 $_GET $_POST 等。 但僅限於獲取用戶請求數據,不應該對數據有任何操作或預處理,這些工作應該交由Models來完成。
  • 調用Models的讀方法,獲取數據,直接傳遞給視圖,供顯示。 當涉及到多個Model時,有關的邏輯應當交給Model來完成。
  • 調用Models的類方法,對Models進行寫操作。
  • 調用視圖渲染函數等,形成對用戶Reqeust的Response。

Model設計參考

在MVC中,Model排第一,是有一定暗示的。一是Model是整個架構中,代碼量最大,複用程度最高, 也是最體現程序員設計功力的地方。 二是View和Controller相對於Model而言,在實際開發中,複用程度不高,邏輯複雜程度較低。 可以說,Model設計得好,整個MVC就好,應用開發起就順。

因此,這一節將以Model爲核心,來講MVC的設計。 實話說,MVC儘管提出了Model View Controller的劃分思想,但到了具體實操中,並不是很好把握的。 下面介紹的設計參考,也僅僅是個人在實際項目中的一些體會和想法,僅作參考。 在具體設計中,可以後把握這麼幾點:

Model應當集中整個應用的數據和業務邏輯

應用當中涉及到的所有業務對象都應儘可能抽像成Model。 如,博客系統當中,文章要抽象成Post,評論要抽象成Comment。 而相關的業務邏輯,如發佈新文章可以用 Post::create() ,刪除評論可以用 Comment::delete() 。 這樣子整個應用就顯得很清晰明瞭。

基礎Model應當儘可能細化

在一個應用中,特別是對於大型複雜應用,Model間關係可能比較複雜。在構造應用時,特別是基礎Model時, 要從足夠小的粒度來設計。 此時,就要考慮採取繼承、封裝等措施了。 比如,一個博客文章Post,一般包含了若干標籤,在頁上一般寫在作者、日期等Post字段的旁邊。 從邏輯上來看,把標籤作爲Post的一個屬性,是說得通的。 但是如果把標籤作爲一個屬性像標題、正文等字段一樣依附於Post。那麼在有的功能上,實現起來是有難度的。 比如,客戶要求,當一個Post含有標籤 “yii, model” 時,可以點擊 “yii” , 然後系統列出所有具標籤中含有 “yii” 的文章。

爲了實現這個功能,正確的設計是單獨將標籤抽象成Tag。這樣,Post和Tag是多對多的關係, 即一個Post有多個Tag,一個Tag也對應多個Post。這個多對多關係可以通過一張數據表 tbl_post_tag 來表示。 接下來,爲Post增加 Post::getTags() 方法,並通過 tbl_post_tag 表來查詢當前Post的所有標籤。 同時,爲Tag增加 Tag::getPosts() 方法,也通過 tbl_post_tag 表來查詢當前Tag對應的文章。 這樣,就具備了實現客戶要求的新功能的基礎。

因此,在Model設計上,要以儘量小的粒度進行設計。一般而言,粒度越小,複用的可能性就越高。

有的讀者可能會問了,既然要求粒度儘可能地小,那麼,Post是不是也應當再細化,把段落抽象爲Model? 是否有這個必要,看客戶需求。一般情況確實沒有這必要,如果這麼做,那是不是再以句子爲單位進行抽象? 但如果客戶要求這個博客系統的評論是針對段落進行的評論的, 要將評論顯示在對應的段落旁邊,甚至顯示每個段落評論人次等功能。那麼就需要把段落抽象成Model了。

分層次設計Model

從設計流程上,數據庫結構設計與Model的設計是緊密相關的。先有數據庫結構設計,後有Model設計。 在設計數據庫結構的時候,也是在設計Model。 一般而言,最單元、粒度最小的Model就是根據每個數據庫表所生成的Model,這往往是個Active Record。

比如標籤的問題,在數據庫存儲過程中,Post和Tag是分開存的,而且這兩個表的字段,沒有冗餘。 tbl_post_tag 表也只記錄他們的ID,沒有實質內容。

在獲取數據渲染視圖,向用戶展現時,這兩個Model及他們的字段,是完全夠用,且沒有冗餘的。

那麼,能不能說 Post 和 Tag 這兩個Model是夠用的呢?顯然還不夠。

當用戶在創建文章、修改文章、審覈文章時,需要採用一個表單來顯示來收集用戶輸入。 其中,對於標籤的採集,一般是一個長條的文本框,讓用戶一次性輸入多個標籤,並以 , 等進行分隔的。

但是,這個文本框沒有一個字段與之進行對應。我們也沒辦法對這個字段的用戶輸入進行任何的驗證、預處理。

因此,Post的功能是不夠用的。不夠用怎麼辦?那就加吧。但直接在 Post 裏面加個 public $tagString 並不好。 畢竟只是在使用表單時,纔會有這個問題,其他場合,這個字段是沒用的。

這種情況下,一般使用繼承:

public class PostForm extends Post
{
    public $tagString;

    ... ...
}

這樣,當控制器發現用戶在創建、修改、審覈文章時,可以使用 PostForm Model來渲染視圖了, 而其他場合則仍使用Post。這樣就在需要時,增加了一個 tagString 的字段用於收集用戶輸入的標籤。

在具體設計過程中,由於Model本身就會包含很多代碼,因此,要多使用這繼承等手段,把代碼組織好。

仔細爲Model方法命名

由於Model的代碼量比較大,又集中了大量的邏輯,因此,會在一個Model中有大量的方法。仍然以Post爲例, 會涉及到創建、審覈、發佈、回收等流程,相關的方法比較多,在命名上要用心。 可能會涉及到的、名字又比較接近的方法就有:

getPrevPost(),前一篇文章,用於導航
getNextPost(),下一篇文章,用於導航
getRelatedPosts($n = 10),獲取相關的N篇文章,用於相關文章推薦列表
getPostsOfAuthor($n = 10),獲取同一作者的N篇相關文章,用於作者文章列表
getLatestPosts($n = 10),最新的N篇文章,靜態方法,用於文章列表或RSS輸出
getHotestPosts($n = 10),最熱門的N篇文章,靜態方法,用於熱門文章列表
getPublishPosts($n = -1),獲取已經發布的N篇文章,靜態方法,用於文章列表
getDraftPosts($n = -1),獲取未發佈的N篇文章,靜態方法,用於作者頁面

這裏只是一些獲取其他Post的方法,命名比較合理,一看就知道意思。 而且全部寫成getter的形式,可以使用讀取屬性的方式進行訪問。

不單單是在Model方法的命名上要用心, 在變量名、類名、方法名等的命名上,也要養成習慣,形成規律。 不要圖一時之快,胡亂起名。否則,出來混,遲早要還的。

MVC與前後端的配合

從MVC的起源來講,是從桌面應用的開發中發展起來的。從本質來講,這是一種解決問題的思路和辦法。 從實踐來講,這是一種久經考驗的有效方式。但是如開頭我們講的,Yii更多的是側重於後端。 對於Web應用而言,包含Yii在內的許多Web開發框架,都是沒有辦法知道用戶的操作,如鼠標、鍵盤等操作的。 Web應用想要了解用戶的操作,只能依靠用戶發送Request。 而對於鼠標、鍵盤等的響應,只能依靠前端技術,如JavaScript等來實現。

再加上這幾年來Web瀏覽器的功能日臻強大。因此,Web應用開發出現了一個新的發展苗頭,就是功能從後端往前端轉移。

在前端,通過JavaScript捕獲用戶操作,進行相應處理。 或是發送回後端獲取響應後處理,如通過ajax請求後端數據,實現無刷新的局部頁面更新,向用戶進行反饋; 或直接在前端由瀏覽器進行處理,如使用backbone.js、Angular.js等前端框架的數據綁定功能等。 這些都使得Web應用表現得越來越像桌面應用。

後端MVC也在爲前後端的發展而改變。 Controller的功能更多的變成了識別是ajax請求還是普通請求, 並根據請求的不同採取相應的視圖渲染方式。對於普通請求,正常渲染視圖,輸出HTML。 對於ajax請求,則返回局部渲染視圖,輸出HTML片段。有的甚至輸出XML或者JSON。 唯一在大潮流中,巍然不動的,還是我們的大Model。

原文出處: http://www.digpage.com/mvc.html 感謝作者的貢獻


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章