Cocoa模型 視圖 控制器設(shè)計模式
本文描述設(shè)計模式在Cocoa框架中的主要實現(xiàn)方式,特別是模型-視圖-控制器和對象建模模式,主要目的是使您對Cocoa的設(shè)計模式有更好的認識,鼓勵您在自己的軟件工程中利用這些模式。
模型-視圖-控制器模式(MVC)是一個相當(dāng)老的設(shè)計模式,它的一些變體至少在Smarttalk的早期就出現(xiàn)了。它是一種高級別的模式,關(guān)注的是應(yīng)用程序的全局架構(gòu),并根據(jù)各種對象在程序中發(fā)揮的作用對其進行分類。它也是個復(fù)合的模式,因為它是由幾個更加基本的模式組成的。
面向?qū)ο蟮某绦蛟谠O(shè)計上采用MVC模式會帶來幾個方面的好處。這種程序中的很多對象可能更具重用性,它們的接口也可能定義得更加良好。程序從總體上更加適應(yīng)需求的改變—換句話說,它們比不基于MVC的程序更加容易擴展。而且,Cocoa中的很多技術(shù)和架構(gòu)—比如綁定技術(shù)、文檔架構(gòu)、和腳本技術(shù)—都基于MVC,而且要求您的定制對象充當(dāng)MVC定義的某種角色。
MVC對象的作用和關(guān)系
MVC設(shè)計模式考慮三種對象:模型對象、視圖對象、和控制器對象。模式定義了這三種對象在應(yīng)用程序中充當(dāng)?shù)慕巧约八鼈兊耐ㄓ嵚窂健T谠O(shè)計應(yīng)用程序時,一個主要的步驟就是進行這三種對象的選擇-或者說為這三種對象創(chuàng)建定制類。三種對象中的每一種都和其它兩種按抽象的邊界區(qū)分,并和其它兩種對象進行跨邊界的通訊。
模型對象負責(zé)包裝數(shù)據(jù)和基本行為
模型對象代表特別的知識和專業(yè)技能,它們負責(zé)保有應(yīng)用程序的數(shù)據(jù)和定義操作數(shù)據(jù)的邏輯。一個定義良好的MVC應(yīng)用程序會將所有重要的數(shù)據(jù)封裝在模型對象中。任何代表應(yīng)用程序留存狀態(tài)的數(shù)據(jù)(無論該狀態(tài)存儲在文件中,還是存儲在數(shù)據(jù)庫中),一旦載入應(yīng)用程序,就應(yīng)該駐留在模型對象中。因為它們代表與特定問題域有關(guān)的知識和專業(yè)技能,所以有可能被重用。
在理想情況下,模型對象不和負責(zé)表示與編輯模型數(shù)據(jù)的用戶界面建立顯式的連接。舉例來說,如果有個代表一個人的模型對象(假定您在編寫一個地址本),您可能希望存儲這個人的生日,則將生日存儲在您的Person模型對象是比較好的做法。但是,日期格式字符串或其它有關(guān)日期如何表示的信息可能存儲在別的地方比較好。
在實踐上,這種分隔并不總是***的,這里有一定的靈活空間。但一般來說,模型對象不應(yīng)該關(guān)心界面和表示的問題。一個具有合理例外的例子是描畫程序,它的模型對象代表要顯示的圖形。圖形對象知道如何描畫自身是合理的,因為它們存在的主要原因就是為了定義視覺上的信息。但是即使在這種情況下,圖形對象也不應(yīng)該完全依賴于特定的視圖,它們不應(yīng)該負責(zé)描畫的具體位置,而應(yīng)該由希望表示這些圖形對象的視圖對象發(fā)出描畫的請求。
進一步閱讀:模型對象實現(xiàn)指南文檔種討論模型對象的正確設(shè)計和實現(xiàn)。
視圖對象負責(zé)向用戶表示信息
視圖對象知道如何顯示應(yīng)用程序的模型數(shù)據(jù),而且可能允許用戶對其進行編輯。視圖對象不應(yīng)該負責(zé)存儲它所顯示的數(shù)據(jù)(這當(dāng)然不是說視圖永遠不存儲它所顯示的數(shù)據(jù)。由于性能上的原因,視圖可能對數(shù)據(jù)進行緩存,或使用類似的技巧)。一個視圖對象可能負責(zé)顯示模型對象的一部分或全部,甚至是很多不同的模型對象。視圖對象可能有很多變化。
視圖對象應(yīng)該盡可能可重用和可配置,它們可以在不同的應(yīng)用程序中提供一致的顯示。在Cocoa中,Application Kit定義了大量的視圖對象,其中很多對象都出現(xiàn)在Interface Builder的選盤上。您可以通過重用Application Kit的視圖對象,比如NSButton對象,來保證應(yīng)用程序中的按鍵和其它Cocoa應(yīng)用程序的按鍵行為是一樣的,從而保證不同的應(yīng)用程序在外觀和行為上具有高度的一致性。
視圖必須正確地顯示模型,因此需要知道模型發(fā)生的改變。由于模型對象不應(yīng)該依賴于特定的視圖對象,所以需要有一個一般性的方式來指示模型對象發(fā)生了變化。
控制器對象連接模型和視圖
控制器對象是應(yīng)用程序的視圖對象和模型對象之間的協(xié)調(diào)者。通常情況下,它們負責(zé)保證視圖可以訪問其顯示的模型,并充當(dāng)交流的管道,使視圖可以了解模型發(fā)生的變化。控制器對象也可以為應(yīng)用程序執(zhí)行配置和協(xié)調(diào)的任務(wù),管理其它對象的生命周期。
在一個典型的Cocoa MVC設(shè)計中,當(dāng)用戶通過某個視圖對象輸入一個值或做出一個選擇時,該值或選擇會傳遞給控制器對象。控制器對象可能以應(yīng)用程序特有的方式對用戶輸入進行解釋,然后或者告訴模型對象如何處理這個輸入—比如“增加一個新值”或“刪除當(dāng)前記錄”,或者使模型對象在其某個屬性上反應(yīng)被改變的值。基于同樣的用戶輸入,一些控制器對象也可以通知相應(yīng)的視圖對象改變其外觀或行為的某個部分,比如禁用某個按鍵。反過來,當(dāng)一個模型對象發(fā)生變化了—比如加入一個新的數(shù)據(jù)源—模型對象通常將變化通知控制器對象,由控制器對象要求一或多個視圖對象進行相應(yīng)的更新。
控制器對象可能是可重用的,也可能是不可重用的,取決于它們的一般類型。"Cocoa控制器對象的類型"部分描述Cocoa中不同類型的控制器對象。
組合角色
我們可以將多個MVC角色組合起來,使一個對象同時充當(dāng)多個角色,比如同時充當(dāng)控制器和視圖對象的角色—在這種情況下,該對象被稱為視圖-控制器。同樣地,您也可以有模型-控制器對象。對于某些應(yīng)用程序,象這樣的角色組合是可接收的設(shè)計。
模型-控制器是主要關(guān)注模型層的控制器。它“擁有”模型,主要責(zé)任是管理模型,并和視圖對象進行交流。應(yīng)用到整個模型的動作方法通常在模型-控制器中實現(xiàn)。文檔架構(gòu)為您提供了一些這樣的方法;比如說,NSDocument對象(文檔架構(gòu)的核心部分)會自動處理和保存文件相關(guān)的動作方法。
視圖控制器是主要關(guān)注視圖層的控制器。它“擁有”界面(視圖),主要責(zé)任是管理界面,并和模型對象進行交流。和視圖顯示的數(shù)據(jù)相關(guān)的動作方法通常在視圖控制器中實現(xiàn)。NSWindowController對象(也是文檔架構(gòu)的核心部分)就是視圖控制器的一個例子。
"MVC應(yīng)用程序設(shè)計指南"中提供一些關(guān)于MVC組合角色對象的設(shè)計建議。
進一步閱讀:基于文檔的應(yīng)用程序概述從另一個角度討論模型控制器和視圖控制器之間的區(qū)別。
Cocoa控制器對象的類型
"控制器對象連接模型和視圖"部分粗略介紹了控制器對象的抽象框架,但是在實踐中的情景要復(fù)雜得多。在Cocoa中有兩種一般類型的控制器對象:仲裁控制器和協(xié)調(diào)控制器。每種類型的控制器對象都和一組不同的類相關(guān)聯(lián),并提供不同的行為。
仲裁控制器通常是從NSController類繼承而來的對象。在Cocoa綁定技術(shù)中使用了這種對象。它們負責(zé)為視圖和模型對象之間的數(shù)據(jù)流提供仲裁或支持。
仲裁控制器通常都是已經(jīng)準備好了,可以直接從Interface Builder選盤中直接拖出。您可以對這些對象進行配置,以在視圖和控制器對象的屬性之間、進而在控制器屬性和模型對象的具體屬性之間建立綁定關(guān)系。結(jié)果,當(dāng)用戶改變視圖對象顯示的值時,新的值就會通過仲裁控制器自動傳遞給模型對象;而當(dāng)模型的屬性值發(fā)生變化時,那些變化又會傳遞給視圖對象。NSController抽象類及其具體子類—NSObjectController、NSArrayController、NSUserDefaultsController、和NSTreeController—提供了諸如提交和丟棄改變的能力,還可以管理選擇和占位值的特性。
協(xié)調(diào)控制器通常是一個NSWindowController或NSDocumentController對象,或者是一個NSObject定制子類的實例。它在應(yīng)用程序中的角色是檢查(或者協(xié)調(diào))整個或部分應(yīng)用程序是否正常工作,比如從一個nib文件解檔出來的對象是否有效。協(xié)調(diào)控制器提供如下服務(wù):
響應(yīng)委托消息和對通告進行觀察
響應(yīng)動作消息
管理自己“擁有”的對象的生命周期(比如在正確的時間釋放那些對象)
建立對象間的連接,并執(zhí)行其它配置任務(wù)
NSWindowController和NSDocumentController類是Cocoa為基于文檔的應(yīng)用程序定義的架構(gòu)的一部分。這些類的實例為上面列出的幾種服務(wù)提供了缺省的實現(xiàn),您也可以通過創(chuàng)建它們的子類來實現(xiàn)更為具體的應(yīng)用程序行為,甚至可以用NSWindowController對象來管理不基于文檔架構(gòu)的應(yīng)用程序窗口。
協(xié)調(diào)控制器通常擁有nib文件中的對象,比如File’s Owner對象,它不屬于nib文件包含的對象,但負責(zé)管理nib文件中的對象。它擁有的對象包括仲裁控制器、協(xié)調(diào)控制器、和視圖對象。如果需要進一步了解File's Owner及類似的協(xié)調(diào)控制器的更多信息,請參見"MVC是一個復(fù)合的設(shè)計模式" 部分的內(nèi)容。
NSObject的定制子類的實例可能完全適合用作協(xié)調(diào)控制器。這種類型的控制器對象既有仲裁的功能,也有協(xié)調(diào)的功能。在仲裁行為方面,它們通過象目標-動作、插座變量、委托、和通告機制來實現(xiàn)視圖和模型對象之間的數(shù)據(jù)移動。它們有可能包含很多“膠水”代碼,由于那些代碼只用于特定的應(yīng)用程序,所以它們是應(yīng)用程序中最不可能被重用的對象。
進一步閱讀:如果您需要進一步了解控制器對象作為仲裁者的角色,請參見"仲裁者"設(shè)計模式的信息;如果需要進一步了解Cocoa綁定技術(shù)的信息。
#p#
MVC是一個復(fù)合的設(shè)計模式
模型-視圖-控制器是一個組合了幾個更為基本的設(shè)計模式的復(fù)合設(shè)計模式。這些基本的模式一起定義了MVC應(yīng)用程序中特有的功能分割和通信路徑。但是和Cocoa相比,傳統(tǒng)意義上的MVC使用了不同的基本模式,主要表現(xiàn)在應(yīng)用程序的控制器和視圖對象的不同作用上。
在原來的(Smalltalk)概念上,MVC是由合成(Composite)、策略(Strategy)、和觀察者(Observer)模式組成的。
合成模式:應(yīng)用程序中的視圖對象實際上是一些嵌套視圖的集合,這些視圖以一種協(xié)調(diào)過的方式(也就是視圖層次結(jié)構(gòu))在一起工作。這些顯示組件包括窗口、復(fù)合視圖(比如表視圖)、以及單獨的視圖(比如按鍵)。用戶輸入和顯示可以在復(fù)合結(jié)構(gòu)的任意級別上進行。
策略模式:一個控制器對象負責(zé)實現(xiàn)一或多個視圖對象的策略。視圖對象將自己限制在視覺效果的維護上,而將與應(yīng)用程序具體界面行為有關(guān)的全部決策委托給控制器。
觀察者模式:模型對象將狀態(tài)的變化通知應(yīng)用程序中感興趣的對象(通常是視圖對象)。
這些模式以圖4-4所示的方式協(xié)同工作:用戶操作視圖層次中某個級別的視圖,結(jié)果產(chǎn)生一個事件。控制器對象接收到這個事件,并根據(jù)應(yīng)用程序的具體邏輯對其進行解釋-也就是說,它應(yīng)用了某種策略。這個策略可以是請求(通過消息)模型對象改變其狀態(tài),也可以是請求視圖對象(位于視圖結(jié)構(gòu)的某個級別上)改變其行為或外觀。反過來,模型對象在狀態(tài)發(fā)生變化時會通知注冊為觀察者的所有對象,如果觀察者是個視圖對象,則可能會因此更新外觀。
圖4-4 傳統(tǒng)版本的MVC是一個復(fù)合設(shè)計模式
Cocoa版本的MVC也是一種復(fù)合模式,和傳統(tǒng)版本有一些類似之處。事實上,基于圖4-4的框圖構(gòu)建一個可以工作的應(yīng)用程序是完全可能的。通過使用綁定技術(shù),您很容易就可以創(chuàng)建一個Cocoa的MVC程序,讓程序中的視圖對象直接觀察模型對象,以接收狀態(tài)的改變。然而這個設(shè)計有個理論上的問題。視圖對象和模型對象應(yīng)該是程序中***可重用性的對象。視圖對象代表操作系統(tǒng)及操作系統(tǒng)支持的應(yīng)用程序的“觀感”;外觀和行為的一致性是很重要的,這就要求對象是高度可重用的。顧名思義,模型對象負責(zé)對問題域的關(guān)聯(lián)數(shù)據(jù)進行封裝,以及執(zhí)行相關(guān)的操作。從設(shè)計的角度上看,***讓模型對象和視圖對象彼此分離,因為這樣可以增加它們的可重用性。
在大多數(shù)Cocoa應(yīng)用程序中,模型對象的狀態(tài)變化通告是通過控制器對象傳遞給視圖對象的。圖4-5顯示了這種不同的機制,盡管多用了兩個基本設(shè)計模式,這種通訊機制顯得清晰很多。
圖4-5 Cocoa版本的MVC也是一個復(fù)合設(shè)計模式
在這種復(fù)合模式中,控制器對象結(jié)合了仲裁者模式和策略模式,對模型和視圖對象之間的數(shù)據(jù)流實施雙向協(xié)調(diào)。模型狀態(tài)的變化通過應(yīng)用程序的控制器對象傳遞給視圖對象。此外,視圖對象在目標-動作機制上采納了命令模式。
請注意:目標-動作機制使視圖對象可以和用戶輸入或選擇進行通訊,這種機制可以在協(xié)調(diào)或仲裁控制器中實現(xiàn)。但是,機制的設(shè)計在不同類型的控制器中也有所不同。對于協(xié)調(diào)控制器,您可以在Interface Builder中將視圖對象連接到它的目標(即控制器對象)上,并為其指定動作選擇器,動作選擇器必須遵循特定的簽名格式。通過成為窗口和全局應(yīng)用程序?qū)ο蟮奈校瑓f(xié)調(diào)控制器也可以進入響應(yīng)者鏈。仲裁控制器使用的綁定機制也是將視圖對象和目標連接起來,并允許動作方法的簽名攜帶可變數(shù)量、任意類型的參數(shù)。但是仲裁控制器不在響應(yīng)者鏈上。
圖4-5描述的是改良后的復(fù)合設(shè)計模式,對其進行改良既有實踐上的原因,也有理論上的原因,特別是在使用仲裁者模式的時候。仲裁控制器是從NSController的具體子類派生而來的,除了實現(xiàn)仲裁者模式之外,這些類還提供很多應(yīng)用程序應(yīng)該加以利用的功能,比如選擇和占位值的管理。如果您不喜歡使用綁定技術(shù),則您的視圖對象可以使用象Cocoa通告中心這樣的機制來接收模型對象的通告。但是這要求您創(chuàng)建一個定制的視圖子類,以便處理模型對象發(fā)出的通告。
在一個設(shè)計良好的Cocoa MVC程序中,協(xié)調(diào)控制器對象常常“擁有”歸檔到nib文件的仲裁控制器。圖4-6顯示了這兩種控制器類型之間的關(guān)系。
圖4-6 協(xié)調(diào)控制器作為nib文件的擁有者
MVC應(yīng)用程序的設(shè)計原則
在設(shè)計應(yīng)用程序的模型-視圖-控制器時,可以應(yīng)用下面這些指導(dǎo)原則:
雖然您可以使用NSObject定制子類的實例來作為仲裁控制器,但是沒有理由重新實現(xiàn)一個仲裁控制器。相反,您可以使用已經(jīng)準備好的、為Cocoa綁定技術(shù)設(shè)計的NSController對象。也就是說,使用NSObjectController、NSArrayController、NSUserDefaultsController、或者NSTreeController實例-或者使用這些NSController具體子類的定制子類的實例來作為仲裁控制器。
然而如果應(yīng)用程序很簡單,而且您對使用插座變量和目標-動作機制來編寫仲裁行為所需要的“膠水代碼”感覺更好的話,也可以使用NSObject定制子類的實例來作為仲裁控制器。在NSObject的定制子類中,您也可以以NSController的方式來實現(xiàn)仲裁控制器,使用鍵-值編碼、鍵-值觀察、以及編輯器協(xié)議。
雖然您可以把不同的MVC角色合并在一個對象中,但是,在總體上***的策略還是保持角色的分離。這可以增強對象的可重用性以及使用這些對象的程序的可擴展性。如果您要把不同的MVC角色合并到一個類中,則首先為該類選擇一個主要的角色,然后(為了便于維護)在相同的實現(xiàn)文件中使用范疇來進行擴展,使其具有其它角色的作用。
設(shè)計良好的MVC應(yīng)用程序的目標之一應(yīng)該是盡可能多地使用(至少在理論上)可重用的對象。特別重要的是,視圖對象和模型對象應(yīng)該是高度可重用的(當(dāng)然,那些準備好的仲裁控制器對象也都是可重用的)。應(yīng)用程序的具體行為通常盡可能多地集中在控制器對象中。
雖然讓視圖直接觀察模型并檢測狀態(tài)的變化是可能的,但是這并不是推薦的做法。視圖對象應(yīng)該總是通過仲裁控制器對象來了解模型對象的變化。這有兩層意義:
如果您使用綁定機制來使視圖對象直接觀察模型對象的屬性,那么您就忽視了NSController及其子類為應(yīng)用程序提供的各種好處:選擇和占位符的管理,還有提交和丟棄修改的能力。
如果您不使用綁定機制,則必須從現(xiàn)有的視圖類派生出子類,并加入處理模型對象發(fā)出的狀態(tài)變化通告的能力。
努力限制應(yīng)用程序中類代碼的依賴關(guān)系。一個類對另一個類的依賴越大,就越不具有重用性。具體的推薦規(guī)則和兩個類在MVC中的角色有關(guān):
視圖對象不應(yīng)依賴于模型對象(雖然對于某些定制視圖來說可能是不可避免的)。
視圖類不必然依賴于仲裁控制器類。
模型類不應(yīng)該依賴于除了其它模型類之外的類。
協(xié)調(diào)控制器不應(yīng)該依賴于模型類(和視圖相似,雖然對某些定制的控制器類來說,這種依賴關(guān)系是必須的。)
仲裁控制器類不應(yīng)該依賴于視圖類或協(xié)調(diào)控制器類。
協(xié)調(diào)控制器類依賴于所有MVC類。
如果Cocoa提供的架構(gòu)已經(jīng)將MVC角色分配給具體類型的對象,則直接使用該架構(gòu)。這樣做可以使您更為容易地將工程的各個組件集成在一起。文檔架構(gòu)就是這樣的一個例子,它包括一個Xcode工程模板,并在模板中將NSDocument對象(基于nib的控制器模型)預(yù)先配置為File's Owner。
Cocoa中的模型-視圖-控制器
模型-視圖-控制器設(shè)計模式使很多Cocoa機制和技術(shù)的基礎(chǔ)。因此,在面向?qū)ο蟮脑O(shè)計中使用MVC的重要性已經(jīng)超過了如何在自己的應(yīng)用程序中得到更好的可重用性和可擴展性。如果您的應(yīng)用程序要使用基于MVC的Cocoa技術(shù),則***它本身也是遵循MVC模式。如果您的應(yīng)用程序很好地進行MVC的分離,在使用這些技術(shù)時就應(yīng)該會相對簡單一些;相反,如果沒有好的分離,則需要花費更多的努力。
Cocoa框架中包含下面這些基于模型-視圖-控制器的架構(gòu)、機制、和技術(shù):
文檔架構(gòu)。在這個架構(gòu)中,一個基于文檔的應(yīng)用程序由一個應(yīng)用程序級別的控制器對象(NSDocumentController)組成的,每個文檔窗口都有一個控制器對象(NSWindowController),每個文檔(NSDocument)都有一個結(jié)合了控制器和模型角色的對象。
綁定技術(shù)。 在之前的討論中曾經(jīng)提到過,MVC是Cocoa綁定技術(shù)的核心。NSController抽象類的具體子類提供了一些準備好的控制器對象,您可以對它們進行配置,建立視圖對象和模型對象屬性之前的綁定關(guān)系。
應(yīng)用程序的腳本能力。在設(shè)計應(yīng)用程序并使其可以支持腳本控制的時候,不僅需要遵循MVC設(shè)計模式,而且需要正確設(shè)計應(yīng)用程序的模型對象。訪問應(yīng)用程序狀態(tài)和請求應(yīng)用程序行為的腳本命令通常應(yīng)該發(fā)送給模型對象或者控制器對象。
Core Data。Core Data框架負責(zé)管理模型對象圖,以及將模型對象存儲到一個持久的倉庫(還有從倉庫中取出),以確保這些對象的持久性。Core Data和Cocoa的綁定技術(shù)緊密結(jié)合在一起。MVC和對象建模模式是Core Data架構(gòu)的基本決定因素。
Undo。在Undo架構(gòu)中,模型對象又一次發(fā)揮中心的作用。模型對象的基元方法(常常是它的存取方法)通常是實現(xiàn)undo和redo操作的地方。某個動作的視圖和控制器對象也可能參與這些操作。舉例來說,您可能有一個方法負責(zé)處理undo和redo菜單項的標題,或者undo一個文本視圖中的選擇操作。
小結(jié):Cocoa模型 視圖 控制器設(shè)計模式的內(nèi)容介紹完了,希望本文對你有所幫助!