第四部分 ? ? ? ? 對 ?象 ?去 ?耦
第11章 ? ? ?中 ?介 ?者
面向對象的設計鼓勵把行為分散到不同對象中。這種分散可能導致對象之間的相互關聯。在最糟糕的情況下,所有對象都彼此了解并相互操作。
雖然把行為分散到不同對象增強了可復用性,但是增加的相互關聯又減少了獲得的益處。增加的關聯使得對象很難或不能在不依賴其他對象的情況下工作。應用程序的整體行為可能難以進行任何重大修改,因為行為分布于許多對象。于是結果可能是創建越來越多的子類,以支持應用程序的任何新行為。
中介者模式用于定義一個集中的場所,對象間的交互可以在一個中介者對象中處理。其他對象不必彼此交互,因此較少了它們之間的依存關系。
中介者模式: ?用一個對象來封裝一系列對象的交互方式。中介者使各對象不需要顯示地相互引用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。
何時使用中介者模式
- ?對象之間的交互雖然定義明確然而非常復雜,導致一組對象彼此相互依賴而且難以理解;
- ?因為對象引用了許多其他對象并與其通訊,導致對象難以復用;
- ?想要定制一個分布在多個類中的邏輯或行為,又不想生成太多子類。
管理 ?TouchPainter 應用程序中的視圖遷移
中介者模式不只適用于把各種對象間錯綜復雜的關系集中化,也適合組織兩個不同視圖間視圖遷移。通過把一個視圖加到另一個視圖之上來管理視圖遷移的iOS應用程序相當常見。這樣第一個視圖需要知道第二個視圖并保持對它的引用,然后是第三個,依次類推。
說明: 中介者模式以中介者內部的復雜性代替交互的復雜性。因為中介者封裝與合并了colleague(同事)的各種協作邏輯,自身可能變得比它們任何一個都復雜。這會讓中介者本身成為無所不知的龐然大物,并且難以維護。
總結: 本章探討了與中介者模式有關的很多內容,也探討了如何使用cocoa ?touch框架用oc來實現這一模式。
雖然對于處理應用程序的行為分散于不同對象并且對象相互依存的情況,中介者模式非常有用,但是應該注意避免讓中介者類過于龐大而難以維護。如果已經這樣子了,可以考慮使用另一種設計模式把它分解。要創造性地混用和組合各種設計模式解決同一個問題。每個設計模式就像一個樂高積木塊。整個應用程序可能要使用彼此配合的各種“積木塊”來創造。
在下一章,將會討論另一種設計模式,它使用一種“發布-訂閱”機制來消除對象耦合。
第12章 ? ? ? ?觀 ? ?察 ? 者
觀察者模式: 定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都得到通知并被自動更新。
何時使用觀察者模式
- ?有兩種抽象類型相互依賴。將它們封裝在各自的對象中,就可以對它們單獨進行改變和復用。
- ?對一個對象的改變需要同時改變其他對象,而不知道具體有多少對象有待改變。
- ?一個對象必須通知其他對象,而它又不需要知道其他對象是什么。
在如下兩處地方使用觀察者模式:
1.在模型-視圖-控制器中使用觀察者模式
2. 在Cocoa Touch框架中使用觀察者模式
cocoa touch ?框架用兩種技術改寫了觀察者模式——通知和鍵值觀察(kvo)。
2.1 ?通知
cocoa touch框架使用NSNotiticationCenter和NSNotification對象實現了一對多的發布-訂閱模型。它們允許主題與觀察者以一種松耦合的方式同學。兩者在通訊時對另一方無需多少了解。
主題要通知其他對象時,需要創建一個可通過全局的名字來識別的通知對象,然后把它投遞到通知中心。通知中心查明特定通知的觀察者,然后通過消息把通知發送給它們。對象訂閱了特定類型的通知時,需要通過選擇器提供一個方法的名字。這個方法必須符合一種單一參數的簽名。方法的參數是通知對象,它包含通知名稱、被觀察的對象以及帶有任何補充信息的字典。當有通知到來時,這個方法會被調用。
模型對象在內部數據改變之后,能夠把通知投遞到通知中心,使消息能夠廣播給其他正在觀察的對象,然后這些對象可作出適當的響應。模型可以像下面這樣構造一個通知然后投遞到通知中心:
NSNotification *notification = [NSNotification ?notificationWithName:@"data ?changes" ? ? ? ? ?object: self];
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter ?postNotification:notification];
通知的實例可以用NSNotification類的類工廠方法,通過指定通知名和作為傳給觀察者的參數的任何對象來創建。在前面的例子中,通知名是@“data ?changes”。確切的名字隨實現而不同。如果主題要傳遞自身作為對象參數,可在創建過程中指定self來實現。
一旦創建了通知,就用它作為[notificationCenter ?postNotification:notification];消息調用的參數,投遞到通知中心。通過向NSNotificationCenter類發送defaultCenter消息,可以得到NSNotificationCenter實例的引用。每個進程只有一個默認的通知中心,所以默認的NSNotificationCenter是個單例對象。defaultCenter是返回應用程序中NSNotificationCenter的唯一默認實例的工廠方法。
任何要訂閱這個通知的對象,首先需要為自己進行注冊。如下面的代碼段所示:
[notificationCenter ?addObserver:self ? ? selector:@selector(update:) ? name:@"data ?changes" ?object:subject];
notificationCenter是用與主題投遞通知的步驟里相同的方法得到的。要注冊觀察者,進行觀察的對象需要在addObserver消息調用中把self注冊為觀察者。它也需要指定選擇器,用以識別在通知中心通知這個作觀察對象時被調用的方法。對收到通知時被調用的方法,作觀察的對象也可以選擇設定所關心的通知的名字,已經任何其他對象作為參數。通知中心用提供的信息來確定應該想做觀察的對象分發何種通知。就我們的例子來說,誒了接受同一個通知,至少它需要指定同樣的通知名。
鍵 - 值 ?觀 ?察
cocoa ?提供了一種稱為鍵值觀察的機制,對象可以通過它得到其他對象特定屬性的變更通知。這種機制在模型-視圖-控制器模式的場景中尤其重要,因為它讓視圖對象可以經由控制器層觀察模型對象的變更。
這一機制基于NSKeyValueObserving非正式協議,cocoa通過這個協議為所有遵守協議的對象提供了一種自動化的屬性觀察能力。要實現自動觀察,參與鍵-值觀察的對象需要符合鍵-值編碼的要求,并且需要符合KVC的存取方法。KVC基于有關非正式協議,通過存取對象屬性實現自動觀察。也可以使用NSKeyValueObserving的方法和相關范疇來實現手段的觀察者通知。對于手動實現,可以禁止默認的自動通知,也可以兩者都保留。
通知和鍵值觀察都是cocoa對觀察者模式的改寫。盡管兩者都依賴同樣的發布者-訂閱者關系,但是它們是為不同的解決方案而設計的。兩者的區別:
通知: 一個中心對象為所有觀察者提供變更通知;主要從廣義上關注程序事件;
鍵值觀察:被觀察的對象直接向觀察者發送通知;綁定于特定對象屬性的值;
現在通過TouchPainter 應用程序,看看如何使用KVO把變更通知從模型(經由控制器)傳遞到視圖
在TouchPainter中更新canvasView上的線條
CanvasViewController: ?控制器 ? ? scribble: ? 模型 ? ? canvasView: ?視圖
因為CanvasViewController的scribble屬性是自動合成的,所以同通常不需要為他提供任何定制的存取方法。但是事情是這樣的:? CanvasViewController依靠scribble發來的更新通知,以進一步指示器canvasView如何畫或重畫scribble中的Mark。它需要用下面的消息調用,將自己加為其私有成員變量scribble的觀察者:
[scribble_ ?addObserver:self ? forKeyPath:@"mark" ?options: NSKeyValueObservingOptionInitial ?| ?NSKeyValueObservingOptionNew ? context:nil];
NSKeyValueObservingOptionInitial選項讓scribble_通知CanvasViewController,在這個消息調用之后立刻提供其mark屬性的初始值。這個選項很重要,因為當scribble對象在其init*方法中進行初始化,第一次設置mark屬性時,CanvasViewController也需要接收通知。NSKeyValueObservingOptionNew選項指示scribble_,每當其mark屬性被設定了新值時通知CanvasViewController。context參數指定可選的對象作為通知的參數。好了,我們回到消息調用本身。問題是,應該把它放在哪兒呢?如果只放在viewDidLoad方法中,那么當客戶端把別的Scribble實例賦給控制器的時候,觀察連接就會斷開。所以它們之間建立連接最好的地方是在scribble_的set存取方法中。它就像一道關口,防止觀察連接被破壞的任何可能。同時,在把別的Scribble引用賦給CanvasViewController之后,存取方法會發一個消息給canvasView_,讓它用新的Scribble引用賦給CanvasViewController之后,存取方法會發一個消息給canvasView_,讓它用新的Scribble中的mark重畫。
所以,設置CanvasViewController與其scribble_之間的觀察連接的部分,并沒有放在viewDidLoad方法中,而是每當控制器被加載時,就在那里創建第一個Scribble實例,并且使用存取方法進行賦值。
如果使用前面幾節中討論的NSNotification和NSNotificationCenter代替KVO來實現同樣功能的話,會是下面這個樣子。
- ?需要為主題和觀察者(scribble_和canvasViewController)定義一個共同的標識符。
- ?當scribble_的內部狀態發生改變時,它會把帶有指定標識符的通知,使用任何必要的對象作為參數(NSNotification的實例)投遞到NSNotificationCenter。
- ?接下來,所有訂閱了標有這個標識符的通知的注冊觀察者,會從NSNotificationCenter收到這一消息。
- ?然后觀察者會在作為回調函數提供給NSNotification的選擇其中處理這一通知。除了使用 框架提供的這兩種方式之外,有些人也許想從零開始構建自己的觀察者基礎設施。
總結:
本章討論了觀察者模式的背景信息以及這一模式的用途。本章也討論了如何在TouchPainter應用程序中為其模型-視圖- 控制器架構實現這一模式,在用戶用手繪圖時把線條的變更反映到屏幕上。
我們也可以不必從頭開始實現整個方案,而是利用cocoa touch框架中使用鍵值觀察(KVO)以及NSNotification和NSNotificationCenter對象實現好的觀察者模式。
在下一個部分,我們將討論幾個用于形成抽象集合結構的設計模式,以及與它們的行為直接相關的其他幾個模式。