這是對(duì)1979年5月12日Trygve Reenskaug提出MVC時(shí)的文章的翻譯。
原始MVC報(bào)告
—— 來自奧斯陸大學(xué)信息學(xué)系Trygve Reenskaug
1978/79年,我在施樂帕洛阿爾托研究實(shí)驗(yàn)室(PARC)做訪問科學(xué)家時(shí),完成了第一個(gè)實(shí)現(xiàn)并編寫了原始的MVC報(bào)告。MVC是作為一個(gè)明顯的解決方案創(chuàng)建的,它解決了從多個(gè)角度給用戶控制其信息的一般問題。MVC已經(jīng)創(chuàng)造了驚人的話題。有些文本甚至使用歪曲的變體,相反的目的是使計(jì)算機(jī)控制用戶。
MVC被認(rèn)為是用戶控制龐大而復(fù)雜的數(shù)據(jù)集問題的一般解決方案。最難的部分是為不同的建筑構(gòu)件打上好名字。Model-View-Editor是第一套。它們?cè)?979年5月12日的第一個(gè)注釋中描述:THING-MODEL-VIEW- EDITOR —— 一個(gè)系統(tǒng)規(guī)劃的例子。
經(jīng)過長(zhǎng)時(shí)間的討論,特別是與阿黛爾·戈德伯格的討論之后,我終于用我在1979年12月10日的第二份說明中描述的術(shù)語“模型-視圖-控制器”(Model-View-Controller):模型-視圖-控制器(Model-VIEWS-CONTROLLERS)。
在我離開施樂PARC之后,Jim Althoff和其他人為Small.-80類庫實(shí)現(xiàn)了一個(gè)MVC版本;我沒有參與這項(xiàng)工作。
奧斯陸,2007年2月12日
Trygve Reenskaug
THING-MODEL-VIEW-EDITOR
一個(gè)規(guī)劃系統(tǒng)的例子
日期:1979年5月12日
由:Trygve Reenskaug
寫給:LRG
歸檔于:[IVY]<Reenskaug>SMALL> TERMIN0LOGY2.DOC
本文的目的是通過一組連貫的例子來探究thing-model-view-editor結(jié)構(gòu)。這些例子都是從我的計(jì)劃系統(tǒng)中提取出來的,并說明了以上四個(gè)概念。所有示例都已實(shí)現(xiàn),盡管與提出的架構(gòu)有所差異。這個(gè)結(jié)構(gòu)與DynaBook在([Ivy]<Reenskaug>DynaBook.doc)中提出的world-Model-view-Tool相對(duì)應(yīng)。
THING
定義
用戶感興趣的東西。它可以是具體的,比如房子或集成電路。也可以是抽象的,就像一個(gè)新的想法或關(guān)于一篇論文的觀點(diǎn)。它可以是一個(gè)整體,像一臺(tái)計(jì)算機(jī),也可以是一個(gè)部件,比如一個(gè)電路元件。
例子:一個(gè)大型項(xiàng)目
假設(shè)Thing是一個(gè)大項(xiàng)目。它可以是一座大橋、一座發(fā)電站或一個(gè)離岸石油生產(chǎn)平臺(tái)的設(shè)計(jì)和建造。
這樣的項(xiàng)目代表復(fù)雜的任務(wù),具有非常多的相互依賴的細(xì)節(jié)。負(fù)責(zé)項(xiàng)目的人員必須檢查所有細(xì)節(jié)及其依賴,以便對(duì)可能出現(xiàn)的各種實(shí)際或建議的情況的后果進(jìn)行推演。
大量不同的抽象被用來幫助控制大型項(xiàng)目。每個(gè)抽象都突出了整個(gè)項(xiàng)目的特定方面,并且單獨(dú)使用或與其他抽象一起用于控制這些方面。抽象的例子是對(duì)材料要求、成本估算、各種預(yù)算和會(huì)計(jì)理論的思考。
活動(dòng)圖(或者說流程圖)或PERT圖對(duì)抽象的過程有很大的幫助。它們能讓我們理清楚應(yīng)該做什么、誰應(yīng)該做、什么時(shí)候要做。
在抽象網(wǎng)絡(luò)中,項(xiàng)目中必須執(zhí)行的每個(gè)任務(wù)都被映射到一個(gè)稱為活動(dòng)的簡(jiǎn)單元素中?;旧?,一個(gè)活動(dòng)包含了它的持續(xù)時(shí)間和它的前置。持續(xù)時(shí)間指的是執(zhí)行相應(yīng)任務(wù)所需的時(shí)間,而前置是在啟動(dòng)當(dāng)前活動(dòng)之前必須完成的活動(dòng)。
這種簡(jiǎn)單的抽象通常以某種方式擴(kuò)展?;诨顒?dòng)的前置,很容易找到每個(gè)活動(dòng)的后繼者。網(wǎng)絡(luò)最開始活動(dòng)都是沒有前置的活動(dòng),而結(jié)束活動(dòng)是沒有后繼者的活動(dòng)。其他信息可以被附加在每個(gè)活動(dòng)上。例如活動(dòng)的資源需求,以及與活動(dòng)相關(guān)的成本和現(xiàn)金流。
MODEL
定義
Model將(上文中的)抽象以數(shù)據(jù)的形式在計(jì)算系統(tǒng)中表述出來。
探討
如上所述,通常存在許多不同的抽象相同事物的方法,因此對(duì)于給定“Thing”的設(shè)置幾個(gè)共存模型通常很有用。或者,人們可以認(rèn)為該項(xiàng)目有一個(gè)大Model,它被細(xì)分成若干個(gè)子Model。

模型在計(jì)算機(jī)中表示為數(shù)據(jù)的集合,以及處理這些數(shù)據(jù)所需的方法。
理想情況下,所有的模型應(yīng)該是完全一致的。這種理想在實(shí)踐中是無法達(dá)到的,這將需要極強(qiáng)的統(tǒng)一性和令人窒息的死板。因此,人們應(yīng)該接受一些不一致之處,其目標(biāo)是整個(gè)模型集應(yīng)當(dāng)是項(xiàng)目的合理準(zhǔn)確的表示,而不必花費(fèi)太多精力在瑣事上。
在我們的示例中,給定的網(wǎng)絡(luò)模型在Smalltalk系統(tǒng)中表示為類NetworkModel的實(shí)例。NetworkModel中的每個(gè)活動(dòng)都被表示為這個(gè)類的實(shí)例。
為類NetworkModel定義的字段之一是activities——活動(dòng)名稱(UniqueString)和活動(dòng)實(shí)例的字典。Activity類定義的字段中有三個(gè)是network(網(wǎng)絡(luò))、predecessors(前置)和successors(后繼者)。network包含指向NetworkModel相關(guān)實(shí)例的指針,predecessors和successors是Activity實(shí)例的指針向量。
ps:這里可以用類似雙向鏈表的概念來理解
我們的模型的整體結(jié)構(gòu)(Smalltalk項(xiàng)目的一個(gè)網(wǎng)絡(luò)模型的表示)如下:

在這個(gè)通用框架中,我們現(xiàn)在可以添加關(guān)于我們的項(xiàng)目的信息,這些信息可以連接到所有network及它的activities。對(duì)于network,我們可以添加字段,例如plannedStart和plannedFinish。對(duì)于Activity類的定義,我們可以添加字段duration、earlyStart、lateStart和resourccRcquirements。我們還可以為lateStart添加字段lateFinish,但決定前請(qǐng)考慮是否真的需要。(無論是duration、earlyStart還是lateStart)。
注意,每個(gè)活動(dòng)的network Model及其子Model不包含關(guān)于如何在屏幕上顯示信息的信息,這能讓項(xiàng)目的抽象Model保持干凈。
VIEW
定義
對(duì)于任何給定的Model,附有一個(gè)或多個(gè)View,每個(gè)View能夠在屏幕上展示Model的一個(gè)或多個(gè)圖形表示。View也能夠?qū)εc該View合理關(guān)聯(lián)的Model發(fā)揮同樣的作用(展示數(shù)據(jù))。
探討
如果能夠遵循 Form-Path- Image(這里我理解為“來源-轉(zhuǎn)換-展示”) 原則,那么所有View的實(shí)現(xiàn)都將被簡(jiǎn)化。
EXAMPLE 1: network列表

一個(gè)View屬于一個(gè)super-network,即網(wǎng)絡(luò)集合。因此,它略微超出目前討論的Model的范圍,但包含在完整性中。上圖顯示了network列表的外觀。
network列表是類NetworkList的一個(gè)實(shí)例,它是ListView的子類。
ListView有一些字段,如frame、itemsList和selectionItem。它能夠在其frame內(nèi)的屏幕上顯示它的itemsList,并對(duì)請(qǐng)求滾動(dòng)的消息作出反應(yīng)。它能夠響應(yīng)在它frame范圍內(nèi)的點(diǎn)擊事件,以及選中某個(gè)item的事件。
因此,ListView與當(dāng)前系統(tǒng)中的ListPane有點(diǎn)相似,但它不是Window的子類,無法調(diào)度。因此,它必須依賴于一個(gè)Editor(Controller的變種)來告訴它它的frame在哪里,并安排滾動(dòng)。選擇的一個(gè)可能的流程是Editor響應(yīng)輸入,并將(點(diǎn)擊的)位置傳遞給ListView。然后要求ListView(或其他View)來選中對(duì)應(yīng)的item。將用戶界面和數(shù)據(jù)處理分離能夠?yàn)镋ditor提供很大的靈活性。
類NetworkList基于ListView構(gòu)建。這個(gè)列表是一個(gè)network列表,并且它必須知道如何持有這樣的列表。此外,它必須能夠執(zhí)行可能與其關(guān)聯(lián)的View相關(guān)的命令。我們只考慮兩個(gè)這樣的命令:返回選中的network名稱,設(shè)置(編輯)選中的network。
EXAMPLE 2: network中的activity列表

network中的activity列表是類ActivityList的實(shí)例,它是ListView的子類(參見上面對(duì)這個(gè)一般類的描述)。
ActivityList的實(shí)例至少必須知道它屬于哪個(gè)network,以及如何獲得該network中所有activity的列表。除了ListView提供的一般選擇機(jī)制之外,沒有特殊命令。
EXAMPLE 3: activity屬性展示

這是一個(gè)activity的所有屬性的文本呈現(xiàn)。它是類ActivityText的一個(gè)實(shí)例,它是TextView的子類。
TextView類中有一些字段,它可以記住它的frame和段落文本。它能夠在框內(nèi)通過wrap-around顯示文本,并響應(yīng)滾動(dòng)文本的消息。它還能夠?qū)⒔o定的坐標(biāo)鏈接到文本內(nèi)的位置,并在給定位置之間選擇文本。再進(jìn)一步,可以要求它響應(yīng)基于選擇的各種操作(如替換、剪切、粘貼等)的消息。
因此,類TextView與當(dāng)前的ParagraphEditor(段落編輯器)類似,但是每個(gè)直接用戶界面都被轉(zhuǎn)化為一個(gè)或多個(gè)消息,以便它可以通過Editor和各種不同的Editor與其他View一起工作。
ActivityText類必須知道如何為給定的activity獲取文本。這個(gè)操作可以有效地由選擇消息觸發(fā),通常從Editor觸發(fā)。它還必須能夠?qū)τ脩敉ㄟ^Editor發(fā)起的關(guān)于activity的消息作出反應(yīng)。一組這樣的消息用于對(duì)View中展示的數(shù)據(jù)進(jìn)行修改,這由父類TextView處理??赡軐?duì)ActivityText本身最重要的命令是,要求View檢查其當(dāng)前內(nèi)容并將數(shù)據(jù)作為activity的屬性的更新傳遞給network Model。
如果這個(gè)View繼承于表格View(listView?)而不是基于當(dāng)前運(yùn)行的文本,則此View可以大大改進(jìn)。
EXAMPLE 4: Network Diagram

上面的圖表是類DiagramView的一個(gè)實(shí)例。這是View中的數(shù)據(jù)不能完全從其Model推導(dǎo)出的唯一示例,并且它必須具有包含所有節(jié)點(diǎn)的形狀和位置的字段,因?yàn)樵撔畔⒉皇荕odel本身的一部分。(存在用于在圖表中自動(dòng)定位節(jié)點(diǎn)的程序,但我們假設(shè)至少需要一些手動(dòng)編輯)。箭頭的位置可以從活動(dòng)之間的依賴關(guān)系推導(dǎo)出來,該信息可以在需要顯示時(shí)從NetworkModel獲取。然而,這樣的過程將非常慢,并且出于效率的原因,我們假設(shè)DiagramView保留該信息的副本。
和所有其他View一樣,DiagramView將需要提供選擇activity所需的兩個(gè)操作,以及滾動(dòng)所需的操作(這次是二維的)。此外,它還需要對(duì)View本身進(jìn)行一些操作,它們與View中節(jié)點(diǎn)的定位有關(guān)。其他操作必須與NetworkModel有關(guān),例如,修改activity的依賴性,并將activity從一個(gè)network傳遞給另一個(gè)network。
EXAMPLE 5: Network Diagram的一個(gè)變化

此View提供了前面示例的替代方案。節(jié)點(diǎn)較小,因此可以在屏幕上呈現(xiàn)更大的網(wǎng)絡(luò)部分。小的節(jié)點(diǎn)使得在節(jié)點(diǎn)中顯示activity名稱變得不切實(shí)際,因此用戶必須通過其他方式獲得該信息。
這個(gè)網(wǎng)絡(luò)圖是在網(wǎng)格背景下呈現(xiàn)的。這是當(dāng)用戶在布置圖表時(shí)使用的,并且點(diǎn)定義了activity節(jié)點(diǎn)的允許位置。
這個(gè)View可以實(shí)現(xiàn)為DiagramView的子類,或者兩者都可以是一些常見View的子類。然而,在本實(shí)現(xiàn)中,兩個(gè)View可以交替地由同一個(gè)對(duì)象生成:DiagramView類包含字段displayType,其值為#large或#small,顯示方式由這個(gè)字段控制,并通過各自的方式顯示在圖表中。
EXAMPLE 6: 甘特圖

這個(gè)View包含垂直軸的activity和水平軸的時(shí)間。在這個(gè)特定的View中,每個(gè)activity對(duì)應(yīng)一個(gè)時(shí)間軸。通過改變陰影,可以較容易控制每個(gè)activity的進(jìn)度,而不會(huì)擾亂整個(gè)項(xiàng)目進(jìn)度。
上圖是GanttView類的實(shí)例,它是ChartView的子類。ChartView包含了圖表的背景:帶有圖例的軸、網(wǎng)格等。它不包含要放入圖表的信息,這里指的是是水平條。然而,它通過提供從外部使用的任何坐標(biāo)系到圖的框架坐標(biāo)的轉(zhuǎn)換方法,幫助其子類呈現(xiàn)該信息。
GanttView類處理用戶的一些消息,最常見的是通過Editor給出的消息。例如viewNetwork:,這個(gè)消息提供了當(dāng)前NetworkModel的名稱。它還能夠返回選中位置所屬的activity,以及選中屬于給定activity的進(jìn)度條。它還能夠傳遞與它相關(guān)的network和activitie上的操作,典型操作與修改當(dāng)前進(jìn)度表有關(guān)。應(yīng)該提供操作來規(guī)劃network(backload:和frontload:)、修改單個(gè)activity的進(jìn)度(plannedStart:和planncdFinish:)以及讓這種修改的結(jié)果正確地執(zhí)行。
NetworkModel必須能夠處理一些操作,比如從GanttPane取出所有activitie的列表。network包含一個(gè)整體的進(jìn)度表,每個(gè)activity有單獨(dú)的進(jìn)度。
EXAMPLE 7: 資源圖

該圖顯示了activitie的資源需求之和的時(shí)間函數(shù)。(一般來說,每個(gè)資源類型都會(huì)有一個(gè)這樣的圖表。)
該圖是類ResourceView的一個(gè)實(shí)例,它是DiagramView的另一個(gè)子類。關(guān)于甘特圖的特性也適用于這一點(diǎn),但選擇的概念需要詳細(xì)闡述。這個(gè)視圖需要可以返回所有目標(biāo)點(diǎn)的x坐標(biāo)上所有需要資源的activity,還需要響應(yīng)選擇的消息:activity可以高亮其所需要的資源需求。對(duì)應(yīng)的,最好將兩者結(jié)合起來,并在所有NetworkModel View中設(shè)置多個(gè)選擇的開關(guān)。
EDITOR(CONTROLLER)
定義
Editor是View和Model之間的接口。它向用戶提供合適的命令系統(tǒng),例如菜單的形式,可以根據(jù)當(dāng)前上下文動(dòng)態(tài)地改變。它為View提供必要的協(xié)調(diào)和命令消息。
探討
用戶通??梢酝瑫r(shí)在屏幕上查看的多個(gè)View以便更好地執(zhí)行任務(wù)。用戶將希望通過點(diǎn)擊、菜單選擇或其他方式來操作這些View。像選擇這樣的命令通常同時(shí)適用于多個(gè)View。Editor的目的是在屏幕上建立和定位一組給定的View,協(xié)調(diào)它們,并為用戶提供合適的命令接口。
EXAMPLE 8: UserView workspace

這是一個(gè)非常通用的Editor,它為運(yùn)行中的Smal1talk系統(tǒng)的任何部分提供用戶界面,這些部分可以從全局變量中訪問。因此,它可以用作規(guī)劃系統(tǒng)的視圖的接口,但它將大部分工作留給用戶。
由于此Editor始終可用,因此它用于啟動(dòng)更專用的Editor,并執(zhí)行不經(jīng)常使用的命令,以保證這種Editor。后者的一個(gè)示例是對(duì)完整數(shù)據(jù)庫的注銷的命令。
EXAMPLE 9: 一個(gè)虛構(gòu)的規(guī)劃Editor

這個(gè)Editor非常類似于之前的Editor,但是它是在network中創(chuàng)建的。因此,該network及其所有activity的消息可以通過方法直接鍵入和執(zhí)行。因此,通過該Editor可以獲得network和activity的完整內(nèi)容。
這個(gè)Editor沒有在這里實(shí)現(xiàn),而是作為下一個(gè)示例中展示的較大Editor的一部分。
EXAMPLE 10: Editor的嵌套

這個(gè)Editor是較大的Editor(見下一個(gè)示例)的一部分,并被設(shè)計(jì)為在其上下文中工作。(與示例9實(shí)際上是同一個(gè)Editor,滾動(dòng)到它的頂部)。
Editor是DemoEditor類的一個(gè)實(shí)例,它是TextEditor的子類。因此,一般文本可以在該Editor中鍵入、存儲(chǔ)和編輯。此外,由于DemoEditor包含了network及其所有activitie,因此這些對(duì)象支持的任何消息都可以通過方法輸入和執(zhí)行。
由于此Editor是更大的Editor(參見下一個(gè)示例)的一部分,所以我們可以執(zhí)行更一般的操作,比如activity持續(xù)時(shí)間,其中activity被解釋為當(dāng)前選擇的activity。
這個(gè)子Editor的一個(gè)變體可以與ListView子類的一個(gè)實(shí)例通訊,通過禁止對(duì)顯示的文本進(jìn)行任何編輯,限制用戶執(zhí)行預(yù)定義命令的可能性。
EXAMPLE 11: 一個(gè)示例Editor

上圖顯示的Editor演示了一個(gè)給定的Model可以通過不同的View來顯示。
這個(gè)Editor是DemoEditor類的實(shí)例,它是PanedWindow的子類。它的每個(gè)組成部分都是一個(gè)與特定View通信的子Editor,并向父Editor發(fā)送和接收命令。
來自:Trygve Reenskaug
日期:1979年12月10日
MODELS - VIEWS - CONTROLLERS
MODELS
Model代表認(rèn)知,Model可以是單個(gè)對(duì)象(單一的),也可以是對(duì)象的某種結(jié)構(gòu)。
一方面,Model及其部分之間應(yīng)該存在一對(duì)一的對(duì)應(yīng)關(guān)系,另一方面,Model與現(xiàn)實(shí)世界應(yīng)該存在一對(duì)一的對(duì)應(yīng)關(guān)系。因此,Model的節(jié)點(diǎn)應(yīng)該代表問題的可識(shí)別部分。
Model的節(jié)點(diǎn)都應(yīng)該處于相同的問題級(jí)別,將面向問題的節(jié)點(diǎn)(例如日程)與實(shí)現(xiàn)細(xì)節(jié)(例如段落)對(duì)應(yīng)起來是令人困惑的,并且被認(rèn)為是不好的形式。
VIEWS
View是其Model的(視覺)表示。它通常會(huì)突出Model的某些屬性,并隱藏其他屬性。因此,它充當(dāng)顯示過濾器。
View被附加到它的Model(或Model部分),并從Model中獲取顯示所需的數(shù)據(jù)。它還可以通過發(fā)送適當(dāng)?shù)南砀翸odel。所有這些獲取方法和消息都必須在Model的定義中,因此View必須知道它所表示的Model的屬性的定義。(這很有可能,例如,它可以獲取Model的標(biāo)識(shí)符或者獲取Text的實(shí)例,它可能不會(huì)假設(shè)Model是Text的子類。)
CONTROLLERS
Controller是用戶和系統(tǒng)之間的鏈接。它為用戶提供輸入,安排相關(guān)View在屏幕上的適當(dāng)位置顯示。它通過向用戶展示菜單或提供命令和數(shù)據(jù)的其他手段來提供用戶輸出的手段。Controller接收這樣的用戶輸出,將其轉(zhuǎn)換為適當(dāng)?shù)南ⅲ⑦@些消息傳遞給一個(gè)或多個(gè)View。
Controller不應(yīng)該對(duì)View進(jìn)行補(bǔ)充,例如不應(yīng)該通過繪制節(jié)點(diǎn)之間的箭頭來連接節(jié)點(diǎn)的View(PS:前面例子中各個(gè)節(jié)點(diǎn)之間的連接線不應(yīng)該由Controller繪制)。
相反,View不應(yīng)該知道用戶輸入,例如鼠標(biāo)操作和擊鍵。應(yīng)該在Controller中編寫一個(gè)方法,該方法向View發(fā)送消息,View準(zhǔn)確地再現(xiàn)用戶命令的任何順序。
EDITORS
Controller連接到它的所有View,它們(View)被稱為Controller的部件。一些View提供了一個(gè)特殊的Controller:一個(gè)Editor,它允許用戶修改View提供的信息。這樣的Editor可以被拼接成Controller和它的View之間的路徑,并且將作為Controller的擴(kuò)展。一旦編輯過程完成,Editor將從路徑中刪除,并被丟棄。
注意,Editor通過連接的View的實(shí)例與用戶通信,因此Editor與View緊密關(guān)聯(lián)。一個(gè)Controller會(huì)通過請(qǐng)求編輯來獲得它的View——沒有其他合適的來源。