confusion

MVC是一個巨大誤會

我是web工程師,從剛開始學MVC就深感困惑:

  • 怎麼每個地方說的MVC都不太一樣?
  • 有些文章講的MVC,跟我正在用的MVC,怎麼像完全不同的東西?

Model、Controller、View三者到底如何互動?真是一個定義不明、含糊不清的名詞。

這讓我研究了很久。最後,發覺它是一個嚴重的誤會。

這個誤會導致了學習和溝通上的代價,請聽我娓娓道來。

哪些是MVC?

web領域,不論前端(client-side)、後端(server-side)、不論什麼程式語言,幾乎所有framework都自稱、或被認為是「MVC」。

有哪些呢?

前端:Backbone.js、AngularJS、Ember.js…

後端:Ruby on Rails、CodeIgniter、Laravel、Django…

真的是這樣嗎?它們全都是MVC嗎?

MVC是什麼?

該怎麼定義MVC呢?

我們先來看看維基百科怎麼說:

MVC模式(Model-View-Controller)是軟體工程中的一種軟體架構模式,把軟體系統分為三個基本部分:模型(Model)、檢視(View)和控制器(Controller)。

嗯,跟大家說的一樣。我們繼續往下看:

模型(Model) 用於封裝與應用程式的業務邏輯相關的資料以及對資料的處理方法。「 Model 」有對資料直接存取的權力,例如對資料庫的存取。「Model」不依賴「View」和「Controller」,也就是說, Model 不關心它會被如何顯示或是如何被操作。但是 Model 中資料的變化一般會通過一種重新整理機制被公布。為了實作這種機制,那些用於監視此 Model 的 View 必須事先在此 Model 上註冊,從而,View 可以了解在資料 Model 上發生的改變。(比較:觀察者模式(軟體設計模式))

看起來有些陌生,整段描述跟你的web開發經驗完全不同,對嗎?

最大的疑問來自這句:

那些用於監視此 Model 的 View 必須事先在此 Model 上註冊,從而,View 可以了解在資料 Model 上發生的改變。(比較:觀察者模式(軟體設計模式))

後面直接叫你去看觀察者模式(observer pattern)。

問題來了:你有在view跟model之間實作observer pattern嗎?

也就是說,你的Model在資料改變之後,能主動通知View嗎?

沒有的話,就根本不符合MVC的定義。

全都不是MVC?

我們現在發現MVC有observer pattern這個必要條件了。

事情嚴重了起來。

client-side framework或許能夠符合這個條件。

Backbone.js官網範例來說,我們可以這樣在Model上註冊:

它的確實作了observer pattern。

但server-side framework呢?

你的Model如何能在發生改變之後去「主動通知」View?

你平常開發web哪有用到server push的技術?

所有server-side framework,從Ruby的Rails;PHP的CodeIgniter、Laravel;到Python的Django,他們全都不是MVC。

它們實作的,是昇陽電腦在1998年提出的「Model 2」。

什麼是Model 2?

Model 2名氣不大,在維基百科連中文條目都沒有。我們看看英文條目怎麼講

Model 2 is a complex design pattern used in the design of Java Web applications which separates the display of content from the logic used to obtain and manipulate the content.

In a Model 2 application, requests from the client browser are passed to the controller. The controller performs any logic necessary to obtain the correct content for display.

它針對web而設計,讓controller進行必要的程序之後,將資料塞進view去呈現。

正是我們server-side框架在做的事情。

也就是說,server-side目前只能實作Model 2;client-side可以實作Model 2,也可以實作MVC。

巨大的代價

web工程師最常碰的就是client-side跟server-side框架。結果整個業界把MVC跟Model 2混為一談,都說成MVC。

這帶來了什麼後果呢?

MVC變成一個從初學者到業界工程師,永遠說不清楚、下不了定義的名詞。

這件事對於學習和討論,造成了非常巨大的成本。(參考下方的Q1和Q2)

那該怎麼辦?

下次有初學者詢問「什麼是MVC」的時候,怎麼回答才不會害他回家之後「越查資料越混亂」?

Rails is not MVC的作者提出了三種解決方法:

第一個方法是聲稱MVC已經從原始意義改變了,Model 2也可以稱為MVC。如此一來,我們可以用「傳統MVC」或「真MVC」來描述原始的MVC。這是現在普遍的作法,但我不認為改變定義是一個好主意。這幾乎是越搞越亂。

第二個方法是到處推廣Rails其實是Model 2,MVC就留給…MVC吧。這很困難,但至少能保持定義不變。

第三個是直接忽略這些混亂。管它那麼多?

我個人覺得MVC這個詞已經沒救了,不管怎麼解釋都會帶給別人混亂。

當對方同時學習client-side跟server-side時,混亂更強烈。

我選擇這樣回答:


MVC有分很多種喔!網路上全部沒寫清楚,你一定看不懂。
沒關係,你只要知道View可以抽出來就好。
C跟M先別管,你先隨便瞎搞吧。


Q&A

Q1: 怎麼可能各大server-side framework都搞錯?

確實有人腦袋清醒得很,它就是Python的Django。

Django的官方文件內根本沒有「Controller」這個名詞。

看看Django官網的常見問題

Q: Django似乎是一個MVC框架,但你們把Controller命名為「View」,把View命名為「Template」。你們幹嘛不用標準命名啊?

A: (前略)…如果你真的很想要一個縮寫,你就說Django是一個MTV框架吧。Model、Template、View。這樣分比較有道理些。

Django不想變成搞亂MVC的幫凶,只好委屈地又發明了一個名詞「MTV」。

Q2: 那client-side框架有受影響嗎?

有。client-side框架也必須為MVC巨大誤會浪費一堆時間解釋。

看看Backbone.js官網的常見問答

Backbone跟「傳統MVC」的關聯何在?

(上略)
…我們來比較一下Backbone跟像是Rails這種server-side MVC框架的差別…
(下略)

Backbone實作了「傳統MVC」,卻被迫用「傳統MVC」來描述server-side的Model 2,然後花一堆篇幅解釋。

Q3: 知道Model 2的存在又如何?我現在依然一片混亂!

沒錯,Model 2跟MVC都用到Model、Controller、View三個名詞,所以看起來類似。

但是,我們不應該再把時間花在思考「MVC怎麼如此難懂」。

我們討論的重點,應該是「如何分辨MVC與Model 2」、「在server-side如何實作Model 2才漂亮」、「在client-side實作MVC跟Model 2的優劣分別何在」。

Q4: 好,那你現在回答我,「如何分辨MVC與Model 2」?

OK,就讓我拋磚引玉一下。
分別談談Model、View、Controller吧:

View

  • Model 2: 不具有行為,只是等別人塞資料進去的模板(template)。
  • MVC: 具有監視Model的行為,並以此去改變呈現(presentation)。

兩種View有沒有很像?跟張飛、岳飛一樣像。

看看Backbone.js官網的View範例。你server-side的View哪是長這樣?

Controller

  • Model 2: 接收請求與參數,轉交給Model處理,再把結果(最新的資料)塞進View。
  • MVC: 接收請求與參數,轉交給Model處理。沒其他事了。

兩種Controller有沒有很像?跟小狗、熱狗一樣像。

MVC的View跟Model 2的Controller可能還比較像一點。(隨便說說,千萬別這樣類比。)

Model

  • Model 2: 接收Controller傳來的參數,回傳結果。
  • MVC: 接收Controller傳來的參數,將結果通知View。

Model倒是有些類似。

總之,Model 2跟MVC除了三個部份的名字一樣之外,沒什麼關聯了。

Q5: 我決定徹底鑽研MVC跟Model 2的定義了。給我一些延伸閱讀?

MVC與Model 2的變異與結合

Rails is not MVC

Django appears to be a MVC framework, but you call the Controller the “view”, and the View the “template”. How come you don’t use the standard names?

How does Backbone relate to “traditional” MVC?

Model-View-Confusion part 1: The View gets its own data from the Model(2015-2-28新增。謝謝網友「王兲玐」的分享。)


一些社群的看法(2015-2-28)

附上幾個社群的連結,裡面有許多很棒的討論。

歡迎提供更多連結給我。

JavaScript.tw

PHP 台灣

Python Taiwan

劉依語 (Mosky)


良葛格針對本文的延伸討論(2015-4-19補充)

技術名詞紛爭多

(Photo via  Julia Wolf, CC License)

  • SC

    為什麼 MVC 要以 Wikipedia 的標準為定義呢?若真的要以維基來看,英文維基上為了免除這個爭議,更直接寫道:
    In some cases an MVC implementation might instead be “passive,” so that other components must poll the model for updates rather than being notified.

    就連中文維基上的語意也沒有說 View 一定要用 server-push 的方式來得知 Model 的改變。後面也是用(比較:…)作為補充參考資料的方式,根本無法推斷 MVC 一定是觀察者模式。

    「我們現在發現MVC有observer pattern這個必要條件了。」這句話完全是無中生有。何況維基也這樣寫著「MVC模式的缺點是由於它沒有明確的定義」。結論:別再標題黨了!

  • Shisha

    個人覺得一般的 server-side framework 比較偏向實作 MVP。ORM 作為 Model 負責存取資料庫,View 則很簡單的用 Template 負責顯示資料,Presenter(或稱作 Controller),作為兩者的橋樑,負責操作 Model 撈資料然後丟給 View 顯示。

    MVC 則是 View 自己去 Model 撈資料,常用的做法是實作 Observer Pattern,當 Controller 變更 Model 時,讓 Model 主動通知 View 。

  • Tonyq Wang

    資料的改變並沒有一定相依於 model 本身,透過 controller 的 event 一樣可以成立。
    你的前提其實是走到 mvvm 去了。如果你看 web 的 mvvm 類型 framework ,也是有的。
    其實 mvc 形式上並沒有太多定義,他是個非常廣泛的定義。所以如果你認定 mvc 是某種特殊形式,就會覺得別的形式好像到處都有人違反 mvc。
    但實務上為了不造成解讀的困難,通常是把 mvc 當成核心概念,特定形式的實作岔出分支來討論不同類型的 mvc 實作。
    另外在 web 上,server 的mvc 跟client 的 mvc, 其實該分開來看,視為一種嵌套。不應該直接混在一起看。

    像是 js 的 mvc ,其實可以視為 server mvc 裡面v 的實作另外又拉出 mvc 層次。(看不同 framework 解讀不同,但我覺得是都能解釋過去的。目前看到的 fw 沒看過 mvc 框不進去的。 )

    • Lono

      「你的前提其實是走到 mvvm 去了」->不對,mvvm是View去觀察ViewModel,而不是View觀察Model。
      作者說的沒錯,MVC的確有一種是 Model 實作觀察者模式,讓View來觀察。

  • elic

    Head First Design Patterns 就有說 Model2 是 Web 版的 MVC 了

  • HBpencil

    哪裡來的結論沒有 observer 就不是 MVC?

  • narcotics726

    講的非常好,讓我對 MVC 又有了深一層的認識。

    但整篇文章的開篇前提卻不太準確。

    Wiki 原文

    > Interactions

    In addition to dividing the application into three kinds of components, the model–view–controller design defines the interactions between them.[13]

    A controller can send commands to the model to update the model’s state (e.g. editing a document). It can also send commands to its associated view to change the view’s presentation of the model (e.g. by scrolling through a document).

    A model stores data that is retrieved according to commands from the controller and displayed in the view.

    A view generates an output presentation to the user based on changes in the model.

    A view controller generates an output view and an embedded controller

    可見並非需要實現觀察者才算是「真正的」MVC,MVC這個名字僅僅確認了架構的劃分思路,并未限制其實現的模式。

  • Shin

    你自己把簡單的事說複雜了… 把 Model 當作資料庫存取的集合, 避免 Controller 裡直接控制資料庫存取, 把結果傳給 View 展出… 就這麼簡單嘛 @@

  • Robert Huang

    標題很聳動,內容卻很平凡。

  • 何宗軒

    非常棒的文章,作者具體的提出問題並且詳細的回答,看完後讓我獲益良多,也重新審視自己在這方面的許多疑惑點

    * 提出不同面向的問題,應該是非常值得被鼓勵的價值
    而不是得過且過,程式能跑就好