深入理解MVC
MVC無人不知,可很多程序員對MVC的概念的理解似乎有誤,換言之他們一直在錯用MVC,儘管即使如此軟體也能被寫出來,然而軟體內部代碼的組織方式卻是不科學的,這會影響到軟體的可維護性、可移植性,代碼的可重用性。
MVC即Model、View、Controller即模型、視圖、控制器。我在和同行討論技術,閱讀別人的代碼時發現,很多程序員傾向於將軟體的業務邏輯放在Controller里,將資料庫訪問操作的代碼放在Model里。
最終軟體(網站)的代碼結構是,View層是界面,Controller層是業務邏輯,Model層是資料庫訪問。
不知道大家知不知道另外一種軟體開發模式三層架構,它和MVC相似之處是也分為三層,分別是UI層表示用戶界面,BLL層表示業務邏輯,DAL層表示數據訪問。三層架構曾經紅極一時,MVC大行其道之後它就銷聲匿跡了, 可現在看來, 它似乎只是改頭換面, 裝扮成MVC的樣子,並且深受程序員們的歡迎,因為它的這種分層方式和前文描述的MVC如出一轍。
再說的直白點,很多程序員將MVC當成了三層架構在用,這看起來似乎沒什麼問題,畢竟三層架構也是一種和MVC齊名的架構模式。可問題在於用三成架構的思路寫MVC,那麼寫出來的東西既不是三成架構也不是MVC,到是像一個什麼都不是四不像。熟悉天龍八部的同學應該知道這樣一段情節,吐蕃番僧鳩摩智強行用道家的小無相功為基礎修鍊少林的七十二絕技和易筋經最終導致走火入魔。其實用這個例子來形容現在一些程序員用三層架構的思想寫MVC最恰當不過了,三層架構的核心思想是面向介面編程和各層之間的解耦和可替換性,MVC框架中沒有這種概念,因為MVC要面對的問題本就不是三成架構要面對的問題,所以以MVC為基礎寫出來的三成架構是不會具備三層架構的核心要義的,換言之,這種代碼是放棄了三層架構和MVC的精華,獲得了它們的糟粕,是愚蠢的編碼方式。
我吐槽了這麼多,對於吐槽的理由要是說不出個所以然來,估計要被人噴死,下面就來說說MVC本質原理和正確使用方式,當然,這裡的MVC指的最純粹MVC,適合各類軟體,而不僅僅指Web框架中的變體MVC,然而萬變不離其宗,文中所述的MVC思想同樣適用於Web開發。
MVC要實現的目標是將軟體用戶界面和業務邏輯分離以使代碼可擴展性、可復用性、可維護性、靈活性加強。
View層是界面,Model層是業務邏輯,Controller層用來調度View層和Model層,將用戶界面和業務邏輯合理的組織在一起,起粘合劑的效果。所以Controller中的內容能少則少,這樣才能提供最大的靈活性。
比方說,有一個View會提交數據給Model進行處理以實現具體的行為,View通常不會直接提交數據給Model,它會先把數據提交給Controller,然後Controller再將數據轉發給Model。假如此時程序業務邏輯的處理方式有變化,那麼只需要在Controller中將原來的Model換成新實現的Model就可以了,控制器的作用就是這麼簡單, 用來將不同的View和不同的Model組織在一起,順便替雙方傳遞消息,僅此而已。
合理的使用MVC有很多好處,要一一道儘是一件異常困難的任務。在這裡我們通過一個反面示例來側面的證明正確使用MVC的好處與依據。
如前文所言, 很多程序員偏愛於將業務邏輯放在Controller中,我極力反對這種做法,現在我就來證明這中做法的錯誤性。
我們知道在寫程序時,業務邏輯的重複使用是經常要面對的場景。 如果業務邏輯寫在控制器中, 要重用它的唯一方法就是將它提升到父類之中,通過繼承來達到代碼復用的效果。但這麼做會帶來一個巨大的副作用,違背了一項重要的面向對象設計原則:介面隔離。
什麼是介面隔離,我在這裡簡單的講述一下。通俗一點講,介面隔離就是當一個類需要繼承另一個類時, 如果被繼承的類中有繼承的類用不到的方法或者屬性時,就不要去實現這個繼承。如果真的情非得已必須要繼承,那麼也需要從被繼承的類中再提取出一個只包含需要部分功能的新類型,最終去繼承這個新類型才是正確的做法。 換句話說,實現繼承的時候,不要去繼承那些用不到的事物。
回到之前的話題,通過繼承父控制器的方式復用業務邏輯時,往往會出現為了重用一個方法而繼承來一大堆用不到的方法,表面上看起來似乎沒什麼問題,但是這會使代碼變的難以理解,
長此以往, 軟體的代碼會朝著不健康的方向發展。要知道,使用繼承的條件是很苛刻的,我們學習面向對象變編程繼承特性時,第一課就是只有滿足IS-A(是一個)關係時才可以使用繼承,如果僅僅是復用代碼,並不是我們使用繼承的理由。使用組合是復用代碼提倡的方式,也就是所謂的HAS-A(有一個)的關係,相信每個程序員都聽過「少用繼承,多有組合」這句話,這句話是軟體開發業的先驅們千錘百鍊總結出來的,值得我們去遵循。
各Model之間是可以相互調用的, Controller也可以無障礙的調用Model,因此將業務邏輯放在Model中可以靈活的使用組合的方式復用代碼。
而Controller之間是不可以相互調用的,要復用代碼只能將代碼提升至父類,通過繼承實現,顯然這種做法既不正確,也不靈活,因此完全不提倡。
綜上所述,僅僅只是代碼復用這一點,也足以將「厚Controller,薄Model」這種不健康的MVC思想打入十八層地獄。
現在我們大概知道了代碼應該如何分布於MVC三層之間, 知其然,並且也知其所以然。接下來我們再從另一個角度深刻剖析MVC,脫它個精光,讓它赤條條展示在我們眼前。
眾所周知,GoF總結過23個設計模式,這23個設計模式是某些特定的編程問題的特效藥,這是業內公認的。
MVC是一種模式,但卻在GoF總結出來的這個23個設計模式之外,確切的說它不是一種設計模式,它是多種設計模式的組合,並不僅僅只是一個單獨的一個模式。
組成MVC的三個模式分別是組合模式、策咯模式、觀察者模式,MVC在軟體開發中發揮的威力,最終離不開這三個模式的默契配合。 那些崇尚設計模式無用論的程序員,請了解只要你們使用MVC,就離不開設計模式。
注意,以下內容以這三個設計模式的知識為基礎,如果對這三個設計模式沒概念,或許會閱讀困難。
先說組合模式在MVC中扮演什麼樣的角色。
組合模式只在視圖層活動, 視圖層的實現用的就是組合模式,當然,這裡指的實現是底層的實現,是由編程框架廠商做的事情,用不著普通程序員插手。
組合模式的類層次結構是樹狀的, 而我們做Web時視圖層是html頁面,html的結構不正是樹狀的嗎,這其實就是一個組合模式的應用,只是瀏覽器廠商已經把界面相關的工作幫我們做掉了,但它確確實實是我們應用MVC的其中一部分,只是我們感覺不到罷了,這也是我們覺得View是實現起來最簡單最沒有歧義的一層的原因。
除網頁以外的其他用戶界面程序,如WPF、Android、http://ASP.NET等等都是使用樹狀結構來組織界面控制項對象的,因為組合模式就是從界面設計的通用解決方案總提煉出來的。所以與其說MVC選擇了組合模式,還不如說組合模式是必定會存在MVC中的,因為只要涉及到用戶界面,組合模式就必定存。事實上即使不理解組合模式,也不影響程序員正確的使用MVC,組合模式本就存在於程序員接觸不到的位置。
然而,觀察者模式和策略模式就顯得比較重要,是實實在在MVC中接觸的到的部分。
觀察者模式有兩部分組成,被觀察的對象和觀察者,觀察者也被稱為監聽者。對應到MVC中,Model是被觀察的對象,View是觀察者,Model層一旦發生變化,View層即被通知更新。View層和Model層互相之間是持有引用的。 我們在開發Web MVC程序時,因為視圖層的html和Model層的業務邏輯之間隔了一個http,所以不能顯示的進行關聯,但是他們觀察者和收聽者的關係卻沒有改變。當View通過http提交數據給伺服器,伺服器上的Model接受到數據執行某些操作,再通過http響應將結果回送給View,View(瀏覽器)接受到數據更新界面,這不正是一個接受到通知並執行更新的行為嗎,是觀察者模式的另一種表現形式。
但是,脫離Web,當通過代碼去純粹的表示一個MVC結構的時候,View和Model間無疑是觀察者和被觀察的關係,是以觀察者模式為理論基礎的。即使在Web中因為http壁壘的原因導致真正的實現有點走樣,但是原理核心和思路哲學卻是不變的。
最後是策略模式。策略模式是View和Controller之間的關係,Controller是View的一個策略,Controller對於View是可替換的, View和Controller的關係是一對多,在實際的開發場景中,也經常會碰到一個View被多個Controller引用,這即使策咯模式的一種體現,只是不那麼直觀而已。
總結一下,關於MVC各層之間關係所對應的設計模式
View層,單獨實現了組合模式
Model層和View層,實現了觀察者模式
View層和Controller層,實現了策咯模式
MVC就是將這三個設計模式在一起用了,將這三個設計模式弄明白,MVC將毫無神秘感可言。如果不了解這三個設計模式去學習MVC,那不管怎麼學總歸是一知半解,用的時候也難免不會出想問題。
再次回到最前面討論的業務邏輯應該放在Controller還是Model的問題上,從設計模式的角度講,策略模式中的策略通常都很小很薄,不會包含太多的內容, Controller是一個策略, 自然不應該在裡面放置過多的內容,否則要替換一個新的會相當麻煩,與此同時也會破壞View-Model的觀察者模式,彷彿View-Controller之間即實現了策略模式又實現了觀察者模式,這種混亂是罪惡的根源,是製造焦油坑讓程序員陷入其中無法自拔的罪魁禍首。切忌,應當避免。
註:此文核心思想來自《head first設計模式》
推薦閱讀:
※Backbone.js 的最佳應用場景有哪些?
※「每日一題」MVC 是什麼?(續1)
※django系列九:頁面布局
※輸入URL後到底發生了什麼?我用小白能明白的方式來說說!