深刻理解MVC架構

MVC

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

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還是主流。ajax

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 並很差。 畢竟只是在使用表單時,纔會有這個問題,其餘場合,這個字段是沒用的。

這種狀況下,通常使用繼承:

1
2
3
4
5
6
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。

相關文章
相關標籤/搜索