第四部分 ? ? ? ? 對 ?象 ?去 ?耦
第11章 ? ? ?中 ?介 ?者
面向?qū)ο蟮脑O計鼓勵把行為分散到不同對象中。這種分散可能導致對象之間的相互關(guān)聯(lián)植阴。在最糟糕的情況下,所有對象都彼此了解并相互操作。
雖然把行為分散到不同對象增強了可復用性,但是增加的相互關(guān)聯(lián)又減少了獲得的益處渊啰。增加的關(guān)聯(lián)使得對象很難或不能在不依賴其他對象的情況下工作。應用程序的整體行為可能難以進行任何重大修改申屹,因為行為分布于許多對象绘证。于是結(jié)果可能是創(chuàng)建越來越多的子類,以支持應用程序的任何新行為独柑。
中介者模式用于定義一個集中的場所,對象間的交互可以在一個中介者對象中處理私植。其他對象不必彼此交互忌栅,因此較少了它們之間的依存關(guān)系。
中介者模式: ?用一個對象來封裝一系列對象的交互方式曲稼。中介者使各對象不需要顯示地相互引用索绪,從而使其耦合松散,而且可以獨立地改變它們之間的交互贫悄。
何時使用中介者模式
- ?對象之間的交互雖然定義明確然而非常復雜瑞驱,導致一組對象彼此相互依賴而且難以理解;
- ?因為對象引用了許多其他對象并與其通訊窄坦,導致對象難以復用唤反;
- ?想要定制一個分布在多個類中的邏輯或行為,又不想生成太多子類鸭津。
管理 ?TouchPainter 應用程序中的視圖遷移
中介者模式不只適用于把各種對象間錯綜復雜的關(guān)系集中化彤侍,也適合組織兩個不同視圖間視圖遷移。通過把一個視圖加到另一個視圖之上來管理視圖遷移的iOS應用程序相當常見逆趋。這樣第一個視圖需要知道第二個視圖并保持對它的引用盏阶,然后是第三個,依次類推闻书。
說明: 中介者模式以中介者內(nèi)部的復雜性代替交互的復雜性名斟。因為中介者封裝與合并了colleague(同事)的各種協(xié)作邏輯脑慧,自身可能變得比它們?nèi)魏我粋€都復雜。這會讓中介者本身成為無所不知的龐然大物砰盐,并且難以維護闷袒。
總結(jié): 本章探討了與中介者模式有關(guān)的很多內(nèi)容,也探討了如何使用cocoa ?touch框架用oc來實現(xiàn)這一模式楞卡。
雖然對于處理應用程序的行為分散于不同對象并且對象相互依存的情況霜运,中介者模式非常有用,但是應該注意避免讓中介者類過于龐大而難以維護蒋腮。如果已經(jīng)這樣子了淘捡,可以考慮使用另一種設計模式把它分解。要創(chuàng)造性地混用和組合各種設計模式解決同一個問題池摧。每個設計模式就像一個樂高積木塊焦除。整個應用程序可能要使用彼此配合的各種“積木塊”來創(chuàng)造。
在下一章作彤,將會討論另一種設計模式膘魄,它使用一種“發(fā)布-訂閱”機制來消除對象耦合。
第12章 ? ? ? ?觀 ? ?察 ? 者
觀察者模式: 定義對象間的一種一對多的依賴關(guān)系竭讳,當一個對象的狀態(tài)發(fā)生改變時创葡,所有依賴于它的對象都得到通知并被自動更新。
何時使用觀察者模式
- ?有兩種抽象類型相互依賴绢慢。將它們封裝在各自的對象中灿渴,就可以對它們單獨進行改變和復用。
- ?對一個對象的改變需要同時改變其他對象胰舆,而不知道具體有多少對象有待改變骚露。
- ?一個對象必須通知其他對象,而它又不需要知道其他對象是什么缚窿。
在如下兩處地方使用觀察者模式:
1.在模型-視圖-控制器中使用觀察者模式
2. 在Cocoa Touch框架中使用觀察者模式
cocoa touch ?框架用兩種技術(shù)改寫了觀察者模式——通知和鍵值觀察(kvo)棘幸。
2.1 ?通知
cocoa touch框架使用NSNotiticationCenter和NSNotification對象實現(xiàn)了一對多的發(fā)布-訂閱模型。它們允許主題與觀察者以一種松耦合的方式同學倦零。兩者在通訊時對另一方無需多少了解误续。
主題要通知其他對象時,需要創(chuàng)建一個可通過全局的名字來識別的通知對象扫茅,然后把它投遞到通知中心女嘲。通知中心查明特定通知的觀察者,然后通過消息把通知發(fā)送給它們诞帐。對象訂閱了特定類型的通知時欣尼,需要通過選擇器提供一個方法的名字。這個方法必須符合一種單一參數(shù)的簽名。方法的參數(shù)是通知對象愕鼓,它包含通知名稱钙态、被觀察的對象以及帶有任何補充信息的字典。當有通知到來時菇晃,這個方法會被調(diào)用册倒。
模型對象在內(nèi)部數(shù)據(jù)改變之后,能夠把通知投遞到通知中心磺送,使消息能夠廣播給其他正在觀察的對象驻子,然后這些對象可作出適當?shù)捻憫DP涂梢韵裣旅孢@樣構(gòu)造一個通知然后投遞到通知中心:
NSNotification *notification = [NSNotification ?notificationWithName:@"data ?changes" ? ? ? ? ?object: self];
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter ?postNotification:notification];
通知的實例可以用NSNotification類的類工廠方法估灿,通過指定通知名和作為傳給觀察者的參數(shù)的任何對象來創(chuàng)建崇呵。在前面的例子中,通知名是@“data ?changes”馅袁。確切的名字隨實現(xiàn)而不同域慷。如果主題要傳遞自身作為對象參數(shù),可在創(chuàng)建過程中指定self來實現(xiàn)汗销。
一旦創(chuàng)建了通知犹褒,就用它作為[notificationCenter ?postNotification:notification];消息調(diào)用的參數(shù),投遞到通知中心弛针。通過向NSNotificationCenter類發(fā)送defaultCenter消息叠骑,可以得到NSNotificationCenter實例的引用。每個進程只有一個默認的通知中心削茁,所以默認的NSNotificationCenter是個單例對象宙枷。defaultCenter是返回應用程序中NSNotificationCenter的唯一默認實例的工廠方法。
任何要訂閱這個通知的對象付材,首先需要為自己進行注冊朦拖。如下面的代碼段所示:
[notificationCenter ?addObserver:self ? ? selector:@selector(update:) ? name:@"data ?changes" ?object:subject];
notificationCenter是用與主題投遞通知的步驟里相同的方法得到的圃阳。要注冊觀察者厌衔,進行觀察的對象需要在addObserver消息調(diào)用中把self注冊為觀察者。它也需要指定選擇器捍岳,用以識別在通知中心通知這個作觀察對象時被調(diào)用的方法富寿。對收到通知時被調(diào)用的方法,作觀察的對象也可以選擇設定所關(guān)心的通知的名字锣夹,已經(jīng)任何其他對象作為參數(shù)页徐。通知中心用提供的信息來確定應該想做觀察的對象分發(fā)何種通知。就我們的例子來說银萍,誒了接受同一個通知变勇,至少它需要指定同樣的通知名。
鍵 - 值 ?觀 ?察
cocoa ?提供了一種稱為鍵值觀察的機制,對象可以通過它得到其他對象特定屬性的變更通知搀绣。這種機制在模型-視圖-控制器模式的場景中尤其重要飞袋,因為它讓視圖對象可以經(jīng)由控制器層觀察模型對象的變更。
這一機制基于NSKeyValueObserving非正式協(xié)議链患,cocoa通過這個協(xié)議為所有遵守協(xié)議的對象提供了一種自動化的屬性觀察能力巧鸭。要實現(xiàn)自動觀察,參與鍵-值觀察的對象需要符合鍵-值編碼的要求麻捻,并且需要符合KVC的存取方法纲仍。KVC基于有關(guān)非正式協(xié)議,通過存取對象屬性實現(xiàn)自動觀察贸毕。也可以使用NSKeyValueObserving的方法和相關(guān)范疇來實現(xiàn)手段的觀察者通知郑叠。對于手動實現(xiàn),可以禁止默認的自動通知崖咨,也可以兩者都保留锻拘。
通知和鍵值觀察都是cocoa對觀察者模式的改寫。盡管兩者都依賴同樣的發(fā)布者-訂閱者關(guān)系击蹲,但是它們是為不同的解決方案而設計的署拟。兩者的區(qū)別:
通知: 一個中心對象為所有觀察者提供變更通知;主要從廣義上關(guān)注程序事件歌豺;
鍵值觀察:被觀察的對象直接向觀察者發(fā)送通知推穷;綁定于特定對象屬性的值;
現(xiàn)在通過TouchPainter 應用程序类咧,看看如何使用KVO把變更通知從模型(經(jīng)由控制器)傳遞到視圖
在TouchPainter中更新canvasView上的線條
CanvasViewController: ?控制器 ? ? scribble: ? 模型 ? ? canvasView: ?視圖
因為CanvasViewController的scribble屬性是自動合成的馒铃,所以同通常不需要為他提供任何定制的存取方法。但是事情是這樣的:? CanvasViewController依靠scribble發(fā)來的更新通知痕惋,以進一步指示器canvasView如何畫或重畫scribble中的Mark区宇。它需要用下面的消息調(diào)用,將自己加為其私有成員變量scribble的觀察者:
[scribble_ ?addObserver:self ? forKeyPath:@"mark" ?options: NSKeyValueObservingOptionInitial ?| ?NSKeyValueObservingOptionNew ? context:nil];
NSKeyValueObservingOptionInitial選項讓scribble_通知CanvasViewController,在這個消息調(diào)用之后立刻提供其mark屬性的初始值值戳。這個選項很重要议谷,因為當scribble對象在其init*方法中進行初始化,第一次設置mark屬性時堕虹,CanvasViewController也需要接收通知卧晓。NSKeyValueObservingOptionNew選項指示scribble_,每當其mark屬性被設定了新值時通知CanvasViewController赴捞。context參數(shù)指定可選的對象作為通知的參數(shù)逼裆。好了,我們回到消息調(diào)用本身赦政。問題是胜宇,應該把它放在哪兒呢?如果只放在viewDidLoad方法中,那么當客戶端把別的Scribble實例賦給控制器的時候桐愉,觀察連接就會斷開封寞。所以它們之間建立連接最好的地方是在scribble_的set存取方法中。它就像一道關(guān)口仅财,防止觀察連接被破壞的任何可能狈究。同時,在把別的Scribble引用賦給CanvasViewController之后盏求,存取方法會發(fā)一個消息給canvasView_抖锥,讓它用新的Scribble引用賦給CanvasViewController之后,存取方法會發(fā)一個消息給canvasView_碎罚,讓它用新的Scribble中的mark重畫磅废。
所以,設置CanvasViewController與其scribble_之間的觀察連接的部分荆烈,并沒有放在viewDidLoad方法中拯勉,而是每當控制器被加載時,就在那里創(chuàng)建第一個Scribble實例憔购,并且使用存取方法進行賦值宫峦。
如果使用前面幾節(jié)中討論的NSNotification和NSNotificationCenter代替KVO來實現(xiàn)同樣功能的話,會是下面這個樣子玫鸟。
- ?需要為主題和觀察者(scribble_和canvasViewController)定義一個共同的標識符导绷。
- ?當scribble_的內(nèi)部狀態(tài)發(fā)生改變時,它會把帶有指定標識符的通知屎飘,使用任何必要的對象作為參數(shù)(NSNotification的實例)投遞到NSNotificationCenter妥曲。
- ?接下來,所有訂閱了標有這個標識符的通知的注冊觀察者钦购,會從NSNotificationCenter收到這一消息檐盟。
- ?然后觀察者會在作為回調(diào)函數(shù)提供給NSNotification的選擇其中處理這一通知。除了使用 框架提供的這兩種方式之外押桃,有些人也許想從零開始構(gòu)建自己的觀察者基礎設施葵萎。
總結(jié):
本章討論了觀察者模式的背景信息以及這一模式的用途。本章也討論了如何在TouchPainter應用程序中為其模型-視圖- 控制器架構(gòu)實現(xiàn)這一模式怨规,在用戶用手繪圖時把線條的變更反映到屏幕上陌宿。
我們也可以不必從頭開始實現(xiàn)整個方案锡足,而是利用cocoa touch框架中使用鍵值觀察(KVO)以及NSNotification和NSNotificationCenter對象實現(xiàn)好的觀察者模式波丰。
在下一個部分,我們將討論幾個用于形成抽象集合結(jié)構(gòu)的設計模式舶得,以及與它們的行為直接相關(guān)的其他幾個模式掰烟。