Any customer can have a car painted any color that he wants so long as it is black.
——Henry Ford, the founder of the Ford Motor Company
對于一個 iOS 開發者來說,學習設計模式的意義主要有兩個。一是理解 Cocoa Touch 框架所使用的各種設計模式,以便更好地使用系統框架;二是學習各個設計模式的原理和實現方式,以便在自己的代碼中應用設計模式,提高工作效率和代碼質量,改善代碼可讀性。這篇短文主要關注前者,講講 Cocoa Touch 都應用了哪些設計模式。
抽象工廠模式 & 類簇
抽象工廠 Abstract Factory:提供一個創建一系列相關或相互依賴對象的接口,而無需指定它們具體的類。
抽象工廠可以將使用者與各種具體的實體類的細節進行解耦。
Cocoa 框架中大量使用的類簇模式 Class Cluster ,通過一個公共的抽象父類來封裝私有的實體子類,是抽象工廠的一種形式。
類簇的例子:
- NSString, NSData, NSDictionary, NSSet, NSArray 以及它們的可變形式。
- NSNumber 在內部被實現為 NSCFNumber、NSCFBoolean 等私有子類,而使用者通常不需要了解這些信息。
Cocoa 框架的類簇模式是抽象工廠的一種形式。抽象父類(例如 NSNumber )聲明了創建它的私有子類(例如 NSCFBoolean)的方法,根據不同的方法(例如 numberWithBool: )來創建各種子類。
適配器模式 & 協議 protocol
適配器 Adapter:將一個類的接口轉換成用戶希望的另外一個接口。適配器模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。
適配器模式中的三種角色:
target:用戶需要的接口,用戶只能夠和 target 交流。
adaptee:能實際實現功能的類。
adapter:用來將 target 和 adaptee 適配。
協議的例子:UITableViewDelegate protocol
- 在 UITableViewDelegate protocol 的使用中,用戶是 UITableView,target 是 protocol,adaptee 是能夠為 UITableView 提供具體功能的 UITableView。
- UITableView 和其他對象通過 UITableViewDelegate protocol 進行交流,所以一個普通的 UIViewController 和 UITableView 無法進行順暢的交流。
- 運用適配器模式,可以創建一個 UIViewController 的子類 MYViewController,由這個子類來實現 UITableViewDelegate protocol。這樣 MYViewController 既能夠使用 UIViewController 的完整功能,又能夠通過 protocol 和 UITableView 進行交流了。
責任鏈模式 & 響應鏈
責任鏈 Chain of Responsibility:使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間發生耦合。此模式將這些對象連成一條鏈,并沿著這條鏈傳遞請求,直到有一個對象處理它為止。
一看到上面的定義你馬上會想到, UIKit 中處理屏幕事件的響應鏈 Responder Chain 顯然是責任鏈模式的一個典型實現。
響應鏈由 UIResponder 的各個子類或后代(UIApplication、UIWindow、UIView)的實例組成。一旦一個 UIView 的實例無法處理特定事件,它會將事件交給它的 next responder 繼續處理。
響應鏈的介紹:Using Responders and the Responder Chain to Handle Events
命令模式:NSInvocation 和 Target-Action 機制
命令:將請求封裝為一個對象,從而可用不同的請求對客戶進行參數化,對請求排隊或記錄請求日志,以及支持可撤銷的操作。
命令模式將操作的生成和執行拆分開。
NSInvocation
命令模式的典型例子。
NSInvocation 的實例可以封裝一條 Objective-C 消息,包含目標對象、方法和方法參數。通過創建 NSInvocation 對象可以得到一個封裝好的請求,可以通過修改對象的屬性來修改請求的各個方面,此后可以通過 - invoke
方法執行,甚至反復執行;還可以將請求對象再次封裝到 NSInvocationOperation 對象內,加入 NSOperationQueue 隊列中執行、暫停和取消。
Target-Action 機制
命令模式的又一個例子,相比 NSInvocation 更輕量化。
給一個 UIControl 對象設置一個 Target-Action 不會導致任務的立即執行,而是當滿足條件時執行。
組合模式 & 視圖層級 View Hierarchy
組合 Composite:將對象組合成樹形結構以表示 部分 - 整體 的層次結構。組合使得用戶對單個對象和組合對象的使用具有一致性。
組合模式便于統一處理組合結構中的所有對象。
視圖層級 View Hierarchy
Cocoa 的視圖層級是組合模式的一個例子,每個視圖既是一個顯示內容的對象,又是一個可以容納子視圖的容器。操作一個視圖時(比如移動或者刪除),不需要了解視圖層級的所有對象,只需要對最頂層的視圖進行操作。
裝飾模式:delegate 和 category
裝飾 decorator:動態地給一個對象添加一些額外的職責。就擴展功能來說,裝飾模式比生成子類更為靈活。
像子類化一樣,裝飾模式可以不用改變原有代碼。它傳達了這樣一條設計原則:對于類,應該更多地擴展它們,而不是常常修改它們。
裝飾模式的特點是:不改變原有類增加功能、和原有類共用一套接口。
delegate 和 category 都不是裝飾模式的嚴格應用,但是體現了這個模式的部分思想。
外觀模式 & UIImage
外觀 Facade:為系統中的一組接口提供一個統一的接口。外觀定義一個高層接口,隱藏了子系統間復雜的通信和依賴關系,讓子系統更易于使用。
UIImage 是應用外觀模式的一個例子。UIImage 是對圖片的一個高層的接口,而圖片的具體實現格式被隱藏起來。UIImage 甚至沒有提供直接獲取底層原始圖片數據的接口,但是用戶可以通過 CGImage 或 CIImage 屬性,或者UIImagePNGRepresentation() 和 UIImageJPEGRepresentation() 函數來獲取合適的圖片表示方式。
迭代器模式 & 枚舉
迭代器 Iterator:提供一種方法順序訪問一個聚合對象中各個元素,而又不需暴露該對象的內部表示。
這個比較容易理解。Cocoa 框架中各個集合類提供的 enumerateObjectsUsingBlock:
方法,以及 for-in 快速枚舉都是迭代器的例子。
中介者模式 & ViewController
中介者 Mediator:用一個對象來封裝一系列對象的交互方式。中介者使各對象不需要顯示地相互引用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。
UITabController 和 UINavigationController 是中介者模式的兩個比較輕量級的應用,它們把不同的 ViewController 的交互集中到自己身上,便于開發者應對大量復雜的交互。如果需要更進一步的解耦合,也可以選擇自己來實現中介者模式。例如:iOS 組件化方案探索。
備忘錄模式: 歸檔和序列化
備忘錄 Memento:在不破壞封裝的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態。這樣以后就可將該對象恢復到原先保存的狀態。
觀察者模式:NSNotification 和 KVO
觀察者 Observer:定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都得到通知并被自動更新。
使用場景:一個對象需要通知其他對象,但不知道有多少其他對象,或者其他對象都是什么。
NSNotification
Cocoa Touch 框架的 NSNotification 機制依照觀察者模式實現了一種一對多的消息廣播。在程序中,可以把對象加入某個通知的觀察者列表。通知發布者會創建一條通知,并把它發布到通知中心。通知中心會通過消息傳遞把通知發送給觀察者列表中的每個對象。
發布消息是一個同步的過程,如果要異步地發布消息,可以把消息加入消息隊列中。
相對于委托(可以在事件發生前決定是否允許事件發生),通知的觀察者只能在事件發生后得到通知,不能對事件造成影響。
Cocoa Touch 框架中的類大量地使用到了通知中心。
KVO(Key-Value Observing)
KVO 允許一個對象通知另一個對象自己特定屬性發生變化,KVO 機制基于 NSKeyValueObserving 非正式協議。被觀察的屬性可以是簡單值、一對一關系或者一對多關系。對于遵從 NSKeyValueObserving 的對象,KVO 的各種方法是由 Cocoa Touch 框架自動實現的。(當然也可以手動實現。)
NSNotification 和 KVO 比較而言,NSNotification 是中心化的,由通知中心統一發送消息,以事件為單位;KVO 則由被觀測對象直接發送消息,通知內容綁定于被觀測對象特定的 property。
代理模式 & NSProxy
代理 Proxy:為其他對象提供一種代理(替代者或者占位符)以控制對這個對象的訪問。
代理一般用于遠程對象、創建成本高昂的對象,或者因安全原因不應該被直接訪問的對象。客戶端可以透明地使用代理。
代理模式和裝飾模式有些相似,但兩者目的不同,代理引入的功能是控制對原對象的訪問。
NSProxy
NSProxy 是一個抽象類,實際上它和 NSObject 一樣是個根類(root class)。NSProxy 定義了作為一個對象的代理所需要的接口。NSProxy 對象一般會把收到的消息直接轉發給它代理的對象,通過子類化可以賦予它更多功能。
例子:YYWeakProxy:使用弱代理解除 retain circle。
單例模式
單例 Singleton:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
不多解釋了。
例子:UIApplication 等。
不嚴格的例子:NSFileManager、NSUserDefaults(可以自己新建實例,嚴格來講不算是單例)。
模板方法模式
模板方法 Template Method:定義一個操作中算法的骨架,而將一些步驟延遲到子類中。模板方法使子類可以重新定義算法的某些特定步驟而不改變該算法的結構。
父類一次性實現算法的不變部分,將可變的部分留給子類來實現。模板方法模式中的控制結構流程是反轉的,父類的模板方法調用子類的操作,而不是子類調用父類的操作。
應用
模板方法模式是 Cocoa Touch 乃至各種面向對象框架的基礎。Cocoa Touch 框架中各個類的編程接口經常包含留給子類來覆蓋的方法。典型的例子,UIView 的 -drawRect:
默認什么也不做,你可以覆蓋它來自定義繪制。
其他例子:
例如 UIViewController 的:prepareForSegue:sender:
。
參考:
設計模式:可復用面向對象軟件的基礎
Objective-C編程之道:iOS設計模式解析
已過期文檔: Cocoa Design Patterns