iOS 繼承的深入探討

繼承

繼承從代碼復(fù)用的角度來說,特別好用,也特別容易被濫用和被錯(cuò)用。不恰當(dāng)?shù)厥褂美^承導(dǎo)致的最大的一個(gè)缺陷特征就是高耦合。
在這里我要補(bǔ)充一點(diǎn),耦合是一個(gè)特征,雖然大部分情況是缺陷的特征,但是當(dāng)耦合成為需求的時(shí)候,耦合就不是缺陷了。

適用繼承的場(chǎng)合

大神Chris Eidhof的文章《subclassing》提到需要自定義UITableViewCell等View視圖的時(shí)候我們可以使用繼承來創(chuàng)建自定義View,這些代碼放入子類更合理,不光代碼得到更好的封裝,也能得到一個(gè)可以在工程中重用的組件。Chris Edihof還提到model可以繼承來實(shí)現(xiàn)了 isEqual: 、hash 、 copyWithZone: 和 description 等方法的類。

當(dāng)我們使用繼承的時(shí)候我們需要考慮

  • 父類只是給子類提供服務(wù),并不涉及子類的業(yè)務(wù)邏輯
  • 層級(jí)關(guān)系明顯,功能劃分清晰,父類和子類各做各的。
  • 父類的所有變化,都需要在子類中體現(xiàn),也就是說此時(shí)耦合已經(jīng)成為需求
  • 我們不能脫離cocoa框架開發(fā),所以我們可以繼承cocoa的類,以達(dá)到快速開發(fā)的目的,但是如果沒有特殊原因我們寫的代碼要控制在繼承鏈不增加兩層。

不適合繼承的場(chǎng)景

  • 當(dāng)你發(fā)現(xiàn)你的繼承超過2層的時(shí)候,你就要好好考慮是否這個(gè)繼承的方案了
  • 不滿足上面一些條件時(shí)候

替代繼承解決復(fù)用需求的解決方案

對(duì)于這樣的問題,業(yè)界其實(shí)早就給出了解決方案:用組合替代繼承,通過定義好的接口進(jìn)行交互,一般來說可以選擇Delegate模式來交互。

協(xié)議


兩個(gè)類并沒有太多共用的代碼,它們只不過具有相同的接口。如果這樣的話,使用協(xié)議可能會(huì)是更好的方案

舉個(gè)例子~
假設(shè)一個(gè)APP有播放器(player)對(duì)象,它擁有播放(play)方法播放視頻,如果APP希望支持YouTube,需要相同幾個(gè)播放(player)接口,

使用繼承實(shí)現(xiàn)代碼如下

<pre>
@interface Player : NSObject

  • (void)play;
  • (void)pause;

@end

@interface YouTubePlayer : Player

@end
</pre>

使用協(xié)議方法代碼如下

<pre>
@protocol VideoPlayer <NSObject>

  • (void)play;
  • (void)pause;

@end

@interface Player : NSObject <VideoPlayer>

@end

@interface YouTubePlayer : NSObject <VideoPlayer>

@end
</pre>

代理

如果當(dāng)我們類中的方法需要些自定義的行為時(shí)候,我們可以使用代理優(yōu)雅解決這個(gè)實(shí)現(xiàn)

舉個(gè)例子~
再以上面的例子為例,player對(duì)象希望在播放的時(shí)候執(zhí)行一些自定義的行為,使用繼承也可以輕易的實(shí)現(xiàn):創(chuàng)建個(gè)player對(duì)象的子類,然后重寫play方法,再調(diào)用[super play],再跟著希望執(zhí)行的行為。但是我們也可以通過的代理的方式更有優(yōu)雅的實(shí)現(xiàn)這個(gè)需求

<pre>
@class Player;

@protocol PlayerDelegate

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

@end

@interface Player : NSObject

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

  • (void)play;
  • (void)pause;

@end
</pre>

現(xiàn)在在player對(duì)象的play方法里,我們可以通過代理屬性調(diào)用 playerDidStartPlaying:方法,任何使用Player類的對(duì)象,可以遵守代理協(xié)議,就可以實(shí)現(xiàn)自定義的playerDidStartPlaying:方法了,player類依然保持它的通用性和獨(dú)立性,方便為對(duì)外提供服務(wù)。代理是非常強(qiáng)大技巧,蘋果本身就經(jīng)常使用。像 UITextField這樣的類,有時(shí)候你還會(huì)想把幾個(gè)不同的方法分組到幾個(gè)單獨(dú)的協(xié)議里,比如UITableView—— 它不僅有一個(gè)代理(delegate),還有一個(gè)數(shù)據(jù)源(dataSource)。

類別

我們有時(shí)候會(huì)給對(duì)象添加方法,通過繼承的方式當(dāng)然可以實(shí)現(xiàn),但是不如category的方式來的方便和容易使用,不增加新的類,可復(fù)用的價(jià)值也更高

注意:
1> 在用類別擴(kuò)展一個(gè)不是你自己的類的時(shí)候,在方法前添加前綴是個(gè)比較好的習(xí)慣做法。如果不這么做,有可能別人也用類別對(duì)此類添加了相同名字的函數(shù)。那時(shí)候程序的行為可能跟你想要的并不一樣,未預(yù)期的事情可能會(huì)發(fā)生。
2> 使用類別還有另外一個(gè)風(fēng)險(xiǎn),那就是,到最后你可能會(huì)使用一大堆的類別,連你自己都會(huì)失去對(duì)代碼全局的認(rèn)識(shí)。假如那樣的話,創(chuàng)建自定義的類可能更簡(jiǎn)單一些。

組合

Casa提到我們盡可能用組合替代繼承。組合是最強(qiáng)大的替代繼承的選項(xiàng)。如果你想復(fù)用已經(jīng)存在的代碼,并且不想共享同樣的接口,組合是最佳選擇

舉個(gè)例子~
假設(shè)你要設(shè)計(jì)一個(gè)緩存類

<pre>
@interface OBJCache : NSObject

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

@end
</pre>

一個(gè)簡(jiǎn)單的做法就通過繼承NSDictionary并且通過調(diào)用字典的方法來實(shí)現(xiàn)這上面兩個(gè)緩存方法。

<pre>
@interface OBJCache : NSDictionary
</pre>

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

更好的方式就是組合了。創(chuàng)建一個(gè)緩存類,并將添加一個(gè)字典的私有屬性,對(duì)外還是暴露著兩個(gè)接口,實(shí)現(xiàn)的時(shí)候就可以通過調(diào)用字典屬性的方法實(shí)現(xiàn)我們使用字典的方法了,這樣做可以靈活改變其涉嫌,而該類的使用者就不用進(jìn)行重構(gòu)。

總結(jié)

代碼復(fù)用,盡管他們都可以通過繼承實(shí)現(xiàn),但是我們?yōu)榱嗽跊]有耦合需求的時(shí)候盡量不要使用繼承,而是根據(jù)不同場(chǎng)景采用不同復(fù)用代碼的方式。如果只是共享接口,我們可以使用協(xié)議;如果是希望共用一個(gè)方法的部分實(shí)現(xiàn),但希望根據(jù)需要執(zhí)行不同的其他行為,我們可以使用代理;如果是添加方法,我們可以優(yōu)先使用類別(category);如果是為了使用一個(gè)類的很多方法,我們可以使用組合來實(shí)現(xiàn)。,如果當(dāng)初只是出于代碼復(fù)用的目的而不區(qū)分類別和場(chǎng)景,就采用繼承是不恰當(dāng)?shù)?。?dāng)你發(fā)現(xiàn)你的繼承超過2層的時(shí)候,你就要好好考慮是否這個(gè)繼承的方案了,第三層繼承正是濫用的開端。確定有必要之后,再進(jìn)行更多層次的繼承。我認(rèn)同Casa的看法:萬不得已不要用繼承,優(yōu)先考慮組合等方式。

相關(guān)鏈接

《subclassing》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容