1. 程式人生 > >Yii框架中MVC設計模式

Yii框架中MVC設計模式

MVC是模型(Model)、檢視(View)、控制器(Controller)3個單詞的縮寫。

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

View的幾個原則

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

Model的幾個原則

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

Controller的設計原則

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

Model設計參考

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。