一、當我們談論設計模式的時候,我們在談什么?
設計模式是為特定場景下的問題而定制的解決方案,是對特定面向對象設計問題主要方面的一種抽象。程序如果在設計中使用了設計模式,將來就更易于復用和擴展、且易于變更。另外,基于設計模式的程序會更加簡潔和高效,因為達成同樣目的所需的代碼會更少。
最早對設計模式做出權威性論述和分類的是 Design Patterns: Elements of Reusable Object-Oriented Software,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著,簡稱 Gang of Four (GoF)。書中論述了根據(jù)創(chuàng)建型(Creational)、結構型(Structural)、行為型(Behavioral)劃分的一共 23 種設計模式。

接下來會結合在 Cocoa Touch 中的應用來談論一些常見的設計模式。
二、MVC——設計模式之王

MVC 幾乎是構建 Cocoa 的基石,也是最廣泛應用的設計模式。它把對象根據(jù)其在應用程序中的作用劃分為三類并鼓勵依此劃分代碼區(qū)域:
- model:代表數(shù)據(jù)結構
- view:用來可視化數(shù)據(jù)結構,或負責與用戶的交互
- controller:協(xié)調 model 和 view,從 model 拿到數(shù)據(jù)并用 view 進行顯示,或者收聽通知并處理數(shù)據(jù)等

MVC 其實并不是一種單一的設計模式,而是許多設計模式的組合。例如,controller 可以視為 controller 和 view 的 Mediator 并應用了 Strategy,view 的結構是 Composite,view 和 controller 的 target-action 模式應用了 Command,controller 通過 Observer 接收 model 發(fā)生變化的通知。
三、Cocoa Touch 中最常見的兩種模式
Delegation
Delegation 并不屬于 GoF 提出的 23 種設計模式中的一種,但是在 Cocoa Touch 中很常見,一般有兩種:
-
view 和 controller 溝通的方式
view 的編程方式一般是 generic 的,因此怎么顯示 view 或得到顯示 view 需要的數(shù)據(jù)源一般通過詢問它的 delegate 完成。通常情況下會將 controller 作為 view 的 delegate(或 data source),例如:
UITableViewUITextFieldUIGestureRecognizer
-
其他對象之間的交互
可以用于任何「不關心對方是什么類型,只要可以作為我的 delegate 能幫我完成一些特定任務」的場合。
這些「特定任務」通過協(xié)議來規(guī)定,因此 Delegation 是基于協(xié)議的。
雖然 Delegation 不是 GoF 提出的一種,但也間接地實現(xiàn)了一些經典的設計模式,例如 Adapter,其含義為將一個接口轉換成客戶希望的另一個接口,使接口不兼容的那些類可以一起工作。類圖如下

典型的 Adapter 模式是會對 Adaptee 類構造一個 wrapper 類也就是 Adapter,并在 Adapter 中實現(xiàn)客戶希望的另一個接口。Delegation 沒有對類進行封裝,但是間接實現(xiàn)了讓接口不兼容的類一起工作的目的。
其他用 Delegation 實現(xiàn)的設計模式將在下文提到。
Observer
Observer 模式定義了對象間的一種一對多依賴關系,使得每當一個對象狀態(tài)發(fā)生改變時,其相關依賴對象皆得到通知并被自動更新。本質上是一種「發(fā)布-訂閱」模型,使得對象和它的觀察者之間解耦,可以在不知道彼此的情況下交流。其類圖如下

在 Cocoa Touch 中有兩種實現(xiàn)方式:
-
Notification
Notification 機制實現(xiàn)了一對多的消息廣播。對象可以向 notification center 注冊成為某具體通知的 observer,這樣當有其他對象發(fā)布了該通知時,notification center 就會通知 observer 對象。
具體通知例如
UIKeyboardWillShow(鍵盤將彈出,這時 UI 可以相應做出變化),UIApplicationDidEnterBackground(app 進入后臺,可以迅速保存一些當前數(shù)據(jù))等等系統(tǒng)通知,或者一些自定義通知。 -
Key-Value Observing (KVO)
KVO 主要用來觀察其他對象屬性的變化。尤其在 MVC 模式中,可以使 view 和 model 的變化保持同步(通過 controller)。KVO 和 Notification 的區(qū)別在于它不需要 notification center,而是直接將變化告知了 observer。Notification 更多地應用于系統(tǒng)通知。
四、經典設計模式在 iOS 中的應用
Creational
Prototype
使用原型實例指定創(chuàng)建對象的種類,并通過復制這個原型創(chuàng)建新的對象。
這種模式很簡單,指無需手動創(chuàng)建、通過復制就可以制造同一類型的多個實例。
在 Cocoa Touch 中的一個典型應用是 UITableViewCell:
Use dynamic prototypes to design one cell and then use it as the template for other cells in the table. Use a dynamic prototype when multiple cells in a table should use the same layout to display information.
要通過原型復制得到新的對象,需要實現(xiàn)深復制協(xié)議。對于 NSObject 的子類,需要實現(xiàn) NSCopying 協(xié)議及其方法 - (id)copyWithZone:(NSZone *)zone。通過調用實例方法 -(id)copy 進行復制,該方法默認會調用 [self copyWithZone:nil]。
Singleton
單例模式保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。

在 Cocoa Touch 中的應用有兩種情況:
-
單一資源
例如 CoreLocation 中的
CLLocationManager類,定義了對 GPS 設備所提供服務的單一訪問點。又如UIScreen.main等。 -
統(tǒng)一管理
例如
FileManager.default、或常見的 MVC-N 模式中的 N(NetworkManager)等。
典型的創(chuàng)建單例的代碼如下:

這里,sharedInstance 是 type property,static 保證了它只初始化一次且線程安全。同時將 init 方法聲明為 private 保證了不能在類外創(chuàng)建該類的對象。
Abstract Factory
抽象工廠模式提供了用于創(chuàng)建一系列相互關聯(lián)的對象的接口,而無須指定它們具體的類。將客戶端和具體類型解耦。類圖如下

在 Cocoa Touch 中有個很重要的應用就是 Class Cluster(類簇)。它封裝了一系列私有具體子類,提供了一個公有抽象超類的接口。抽象超類聲明了一系列創(chuàng)建私有子類對象的方法,再根據(jù)不同的方法動態(tài)選擇合適的具體子類類型。
Class clusters in Cocoa can generate only objects whose storage of data can vary depending on circumstances.
在 Foundation 框架中,NSNumber、NSString、NSData、NSDictionary、NSSet 和 NSArray 以及它們的 mutable 版本都是基于類簇實現(xiàn)的。
拿 NSNumber 舉例來說,該抽象超類聲明了一系列創(chuàng)建對象的方法:

不同方法返回的對象可能屬于不同的私有子類,其具體類型對調用者不可見。
With class clusters there is a trade-off between simplicity and extensibility.
類簇簡化了類的接口,使得學習和使用類變得更容易。然而,自定義抽象超類的子類也變得復雜。
Structural
Decorator
用于動態(tài)地給對象增加一些額外的職責。這種方式比生成子類的實現(xiàn)更為靈活。類圖如下

裝飾通過包裝類的對象來擴展其行為,詮釋了設計原則:
Classes should be open to extension but closed to modification.
在 Cocoa Touch 中應用了這一設計原則的類例如 UIScrollView 和 UIDatePicker,它們是復合視圖,結合了其他類的簡單視圖對象并協(xié)調其交互。
另外,基于這一設計模式,有兩種典型的實現(xiàn)方式:
-
Category
它是Objective-C語言的特性,可以動態(tài)向類或結構添加額外的方法,而無需子類化。運行時,通過這種方式添加的方法與類中定義的原始方法沒有任何差別。因此這種方式添加的方法也會被子類繼承。尤其好的一點是,它可以用于看不到源碼的類。需要注意的是:
- 不能向類添加實例變量
- 如果添加的方法覆蓋了類中已有的方法,運行時行為將不可預測
Category 并沒有嚴格地實現(xiàn)裝飾模式,沒有包裝類的對象,也沒有動態(tài)擴展類而是一種編譯時的特性。它通過其他方式實現(xiàn)了裝飾模式的目的。
-
Delegation
前文有介紹過,同樣地,它也不是裝飾模式的嚴格實現(xiàn),沒有對類的對象進行包裝,而是讓類將一些指定行為動態(tài)交給類的 delegate 去完成。除了 delegation 定義的方法,沒有其他共享的接口。
Composite
組合模式將對象組合成樹形結構來表示「部分-整體」的層次結構,統(tǒng)一了訪問單個對象和組合對象的接口。

在 Cocoa Touch 中典型的應用就是 View Hierarchy:

view 可以添加其他 view 作為 subview,同時這些 subview 又可以作為其他 view 的 superview。每個 view 都只有一個 superview,可以有任意數(shù)量的 subview。這樣就形成了樹狀結構。這種結構的好處是方便遍歷:
-
drawing
當 window 需要顯示時,superview 會在 subview 之前進行 render。當有消息發(fā)送給 view,消息會進一步發(fā)送給 subview。這樣 view hierarchy 就可以當做一個整體的 view 來對待。
-
event handling
用于形成 responder chain 來處理各種事件,詳見 Chain of Responsibility 模式。
Proxy
用于控制對其他對象的訪問。

Cocoa Touch 中有個 NSProxy 類,用來作其他對象的代理。有多種用途:
-
lazy instantiation
proxy 對象作為占位符,實現(xiàn)昂貴資源的懶加載。例如 Mail app 中,較大的附件資源通常先顯示為占位符,當用戶點擊之后再去真正地下載附件。
-
method forwarding
proxy 對象將消息轉發(fā)給其代理的對象,可以用這種機制類似地實現(xiàn)多重繼承。
-
sentry objects for security
為了保證安全性,先由 proxy 對象檢查訪問權限。
Facade
外觀模式為子系統(tǒng)中的一組接口提供一個統(tǒng)一的高層接口,讓子系統(tǒng)更易于使用。

在 Cocoa Touch 中的一個例子是 UIImage,它提供了一個統(tǒng)一的接口來加載圖片,將圖片格式的細節(jié)隱藏起來。
Behavioral
Chain of Responsibility
責任鏈模式將多個對象連成一條鏈,并沿著這條鏈傳遞請求,直到有一個對象響應為止。使多個對象都有機會處理請求,從而避免請求的發(fā)送者和接收者之間發(fā)生耦合。

在 Cocoa Touch 中的典型應用是 responder chain(響應鏈)。下圖是含 label、button 等的例子及其默認的響應鏈:

當 app 接收了某種 event,UIKit 會自動將該 event 傳給 first responder。如果它不能處理 event,會把 event 發(fā)送給其 next responder。未處理的 event 會沿著響應鏈傳遞,直到某個 responder 可以處理為止。
細節(jié)可以參考官方文檔 Understanding Event Handling, Responders, and the Responder Chain。
Command
命令模式將一個請求封裝為一個對象,對請求排隊或者記錄請求日志,支持可撤銷的操作。它將提出請求的對象與接受并執(zhí)行請求的對象解耦。類圖如下

用 NSInvocation 類來封裝 Objective-C 的消息(也包括目標對象和參數(shù)等)。在 Cocoa Touch 中有兩個應用:
-
Target-Action 機制
在這種機制下,一個 control 對象(例如 button、slider 或 tex field 等)會向其 target 發(fā)送封裝好的 action 消息。target 接收后將對消息進行處理。
-
NSUndoManager類用于撤銷操作和恢復操作。它管理了一個撤銷棧和恢復棧,當命令執(zhí)行后,就將其 push 到撤銷棧。如果需要撤銷,則從撤銷棧中 pop 一個命令,并 push 到恢復棧。如果需要恢復,就從恢復棧中 pop 一個命令,再 push 到撤銷棧。兩個棧配合使用,可以方便地執(zhí)行撤銷和恢復操作。
Mediator
用一個對象(中介者)來封裝一系列對象的交互方式,使各對象不需要顯式地相互引用從而解耦,且可以獨立地改變它們之間的交互。

典型應用就是 MVC 模式中的 controller。在 Cocoa Touch 中,負責這一角色的是 UIViewController,來協(xié)調 model 和 view 的交互。 同樣地,對于多個 view controller,也有 UINavigationController 和 UITabBarController 來作為其中介者。

Memento
備忘錄模式用于保存數(shù)據(jù)和恢復數(shù)據(jù)。在不破壞封裝的前提下捕獲一個對象的內部狀態(tài),并在該對象之外保存這個狀態(tài),方便以后將該對象恢復到原先保存的狀態(tài)。類圖如下

在 Cocoa Touch 中的應用包括:
-
State Restoration
當 app 退出時,可以通過保存當前 state 以在下次打開該 app 時恢復到退出前的狀態(tài)。
-
Archiving
歸檔操作將對象類型及其屬性保存為一個歸檔文件,可以保存在文件系統(tǒng)中或進行網(wǎng)絡傳輸。通常需要對 MVC 中的 model 進行 encode 歸檔,再從該歸檔 decode 讀取 model。 可以分別利用
NSKeyedArchiver和NSKeyedUnarchiver,并遵從NSCoding協(xié)議。
參考文獻
最后,附上參考文獻:
- 官方文檔 Cocoa Design Patterns
- 設計模式的書(本文截圖出處)Pro Objective-C Design Patterns for iOS
- 開源項目 Design-Patterns-In-Swift