鍍金池/ 教程/ iOS/ 子類
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動布局工具箱
動畫
為 iOS 7 重新設(shè)計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應(yīng)式 Android 應(yīng)用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動畫
常見的后臺實(shí)踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學(xué)無止境
XCTest 測試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

子類

這篇文章跟我以往的文章有點(diǎn)不一樣。它主要是一些思想與模式的匯集,而不是一篇指南。下面我所寫的模式幾乎全都來之不易,都是我犯了錯之后才學(xué)到的。我并不認(rèn)為自己是子類方面的權(quán)威,但我確實(shí)想把我學(xué)到的一些東西分享出來。別把本文當(dāng)做權(quán)威指南,它只是一些例子的匯集。

在被問到 OOP(面向?qū)ο缶幊蹋┑臅r候,Alan Kay(OOP 的發(fā)明人)寫到:它跟類無關(guān),但跟消息有關(guān)。^1然而,很多人的關(guān)注點(diǎn)仍然還在類層次上。在本文中,我們會看幾個我們可能會把注意力放在創(chuàng)建復(fù)雜的類結(jié)構(gòu)上的例子,并給出更有用的替代方案。根據(jù)經(jīng)驗(yàn),這樣會讓代碼更簡單,更易維護(hù)。關(guān)于這個話題,在 Clean Code(中文版:代碼整潔之道)和 Code Complete(中文版:代碼大全)中已經(jīng)有大量討論。推薦你閱讀這兩本書。

何時用子類

首先,我們討論幾種使用子類比較合適的場景。如果你要寫一個自定義布局的 UITableViewCell ,那就創(chuàng)建一個子類。這同樣適用于幾乎每個視圖。一旦你開始布局,把這塊代碼放入子類就更合理一些,不光代碼得到了更好的封裝,你也能得到一個可在工程之間重用的組件。

假設(shè)你的代碼是針對多平臺多版本的,并且你需要針對每個平臺每個版本寫一些代碼。這時候更合理的做法可能是創(chuàng)建一個 OBJDevice 類,讓一些子類如 OBJIPhoneDeviceOBJIPadDevice ,甚至更深層的子類如 OBJIPhone5Device 來繼承,并讓這些子類重寫特定的方法。例如,你的 OBJDevice 類可能包含了函數(shù) applyRoundedCornersToView:withRadius ,它有一個默認(rèn)的實(shí)現(xiàn),但是也能被特定的子類重寫。

另一個子類化可能很有用的場景是模型對象(model object)。絕大多數(shù)情況下,我的模型對象繼承自一個實(shí)現(xiàn)了 isEqual: 、 hash 、 copyWithZone:description 等方法的類。這些方法只被實(shí)現(xiàn)一次,并且迭代循環(huán)遍歷所有屬性,所以極不容易出錯。(如果你也想找一個這樣的基類,可以考慮使用 Mantle ,它就是這么做的,并且做得更多。)

何時不使用子類

在以往工作過的很多工程中,我見到過很多繼承層次很深的子類。當(dāng)我也這么干的時候,總會感到內(nèi)疚。除非繼承的層次非常淺,否則你會很快發(fā)現(xiàn)它的局限性。^2

幸運(yùn)的是,如果你發(fā)現(xiàn)自己正在使用深層次的繼承,還有很多替代方案可選。在下面的章節(jié)中,我們會逐個進(jìn)行更詳細(xì)地描述。如果你的子類只是使用相同的接口,協(xié)議會是個非常好的替代方案。如果你知道某個對象需要大量的修改,你可能會使用代理來動態(tài)改變和配置它。當(dāng)你想給已有對象增加一些簡單功能時,類別可能是個選擇。當(dāng)你有一堆重寫了相同方法的子類時,你可以使用配置對象(configuration object)來代替。最后,當(dāng)你想重用某些功能時,組合多個對象而不是擴(kuò)展它們可能會更好。

替代方案

替代方案:協(xié)議(Protocols)

很多時候,使用子類的原因是你想保證某個對象可以響應(yīng)某些消息。假設(shè)在 app 里你有一個播放器對象,它可以播放視頻。現(xiàn)在你想添加對 YouTube 的支持,使用相同的接口,但是具體實(shí)現(xiàn)不同。你可以使像這樣用子類來實(shí)現(xiàn):

@class Player : NSObject

- (void)play;
- (void)pause;

@end

@class YouTubePlayer : Player

@end

事實(shí)上可能這兩個類并沒有太多共用的代碼,它們只不過具有相同的接口。如果這樣的話,使用協(xié)議可能會是更好的方案??梢赃@樣用協(xié)議來寫你的代碼:

@protocol VideoPlayer <NSObject>

- (void)play;
- (void)pause;

@end

@class Player : NSObject <VideoPlayer>

@end

@class YouTubePlayer : NSObject <VideoPlayer>

@end

這樣,YouTubePlayer 類就不必知道 Player 類的內(nèi)部實(shí)現(xiàn)了。

替代方案:代理(Delegation)

再一次假設(shè)你有一個像上面例子中的 Player 類?,F(xiàn)在,你想在開始播放的時候在某個地方執(zhí)行一個自定義的函數(shù)。這么做相對容易一些:創(chuàng)建一個自定義的子類,重寫 play 方法,調(diào)用 [super play ],然后開始做你自定義的工作。這么做是一種方法。另外一種方法是,改動你的 Player 對象,然后給它設(shè)置一個代理。如下:

@class Player;

@protocol PlayerDelegate

- (void)playerDidStartPlaying:(Player *)player;

@end

@class Player : NSObject

@property (nonatomic,weak) id<PlayerDelegate> delegate;

- (void)play;
- (void)pause;

@end

現(xiàn)在,在播放器的 play 方法里,就可以給代理發(fā)送 playerDidStartPlaying: 消息了。這個 Player 類的任何使用者都可以僅僅實(shí)現(xiàn)這個代理協(xié)議,而不用繼承該該類, Player 類也能夠保持通用性。這是個強(qiáng)大有效的技術(shù),蘋果在自己的框架里大量地使用它。你想想像 UITextField 這樣的類,還有 NSLayoutManager。有時候你還會想把幾個不同的方法打包分組到幾個單獨(dú)的協(xié)議里,比如 UITableView —— 它不僅有一個代理(delegate),還有一個數(shù)據(jù)源(dataSource)。

替代方案:類別(Categories)

有時候,你可能會想給一個對象增加一點(diǎn)點(diǎn)額外的功能。比如你想給 NSArray 增加一個方法 arrayByRemovingFirstObject。不用子類,你可以把這個函數(shù)放到一個類別里。像這樣:

@interface NSArray (OBJExtras)

- (void)obj_arrayByRemovingFirstObject;

@end

在用類別擴(kuò)展一個不是你自己的類的時候,在方法前添加前綴是個比較好的習(xí)慣做法。如果不這么做,有可能別人也用類別對此類添加了相同名字的函數(shù)。那時候程序的行為可能跟你想要的并不一樣,未預(yù)期的事情可能會發(fā)生。

使用類別還有另外一個風(fēng)險,那就是,到最后你可能會使用一大堆的類別,連你自己都會失去對代碼全局的認(rèn)識。假如那樣的話,創(chuàng)建自定義的類可能更簡單一些。

替代方案:配置對象(Configuration Objects)

在我經(jīng)常會犯的錯誤中(現(xiàn)在很快就能發(fā)現(xiàn)了),其中有一條是:使用一個含有幾個抽象方法的類并讓很多子類來重寫某個方法。例如,在一個幻燈片應(yīng)用里,你有一個主題類 Theme ,它有幾個屬性,比如 backgroundColorfont ,還有一些在一張幻燈片上如何布局的邏輯函數(shù)。

然后,對每種主題,你都創(chuàng)建一個 Theme 的子類,重寫某個函數(shù)(例如 setup )并配置其屬性。直接使用父類對此做不了什么事。在這種情況下,你可以使用配置對象來讓代碼更簡單些。你可以把共有的邏輯(比如幻燈片布局)放在 Theme 類中,把屬性的配置放到較簡單的對象中,這些對象中只含有這些屬性。

例如,類 ThemeConfiguration 具有 backgroundColorfont 屬性,而類 Theme 在其初始化函數(shù)中獲取一個配置類 ThemeConfiguration 的值。

替代方案:組合

組合是代替子類化的最強(qiáng)大有效的方案。如果你想重用已有代碼而不想共享同樣的接口,組合就是你的首選武器。例如,假設(shè)你要設(shè)計一個緩存類:

@interface OBJCache : NSObject

- (void)cacheValue:(id)value forKey:(NSString *)key;
- (void)removeCachedValueForKey:(NSString *)key;

@end

簡單點(diǎn)的做法是直接繼承 NSDictionary,通過調(diào)用字典的函數(shù)來實(shí)現(xiàn)上面的兩個方法。

@interface OBJCache : NSDictionary

但是這么做有幾個弊端。它本來是應(yīng)該被詳細(xì)實(shí)現(xiàn)的,但只是通過字典來實(shí)現(xiàn)?,F(xiàn)在,在任何需要一個 NSDictionary 參數(shù)的時候,你可以直接提供一個 OBJCache 值。但如果你想把它轉(zhuǎn)為其它完全不同的東西(例如你自己的庫),你就可能需要重構(gòu)很多代碼了。

更好的方式是,將這個字典存在一個私有屬性(或者實(shí)例變量)中,對外僅僅暴露這兩個 cache 方法?,F(xiàn)在,當(dāng)你有了更深入想法的時候,你可以在靈活地修改其實(shí)現(xiàn),而該類的使用者們不用進(jìn)行重構(gòu)。