iOS 繼承的深入探討

繼承

繼承從代碼復(fù)用的角度來說百姓,特別好用,也特別容易被濫用和被錯用况木。不恰當(dāng)?shù)厥褂美^承導(dǎo)致的最大的一個缺陷特征就是高耦合垒拢。
在這里我要補充一點,耦合是一個特征火惊,雖然大部分情況是缺陷的特征求类,但是當(dāng)耦合成為需求的時候,耦合就不是缺陷了矗晃。

適用繼承的場合

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

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

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

不適合繼承的場景

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

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

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

協(xié)議


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

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

使用繼承實現(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)我們類中的方法需要些自定義的行為時候家卖,我們可以使用代理優(yōu)雅解決這個實現(xiàn)

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

<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對象的play方法里,我們可以通過代理屬性調(diào)用 playerDidStartPlaying:方法纳账,任何使用Player類的對象逛薇,可以遵守代理協(xié)議,就可以實現(xiàn)自定義的playerDidStartPlaying:方法了疏虫,player類依然保持它的通用性和獨立性永罚,方便為對外提供服務(wù)啤呼。代理是非常強大技巧,蘋果本身就經(jīng)常使用呢袱。像 UITextField這樣的類官扣,有時候你還會想把幾個不同的方法分組到幾個單獨的協(xié)議里,比如UITableView—— 它不僅有一個代理(delegate)羞福,還有一個數(shù)據(jù)源(dataSource)惕蹄。

類別

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

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

組合

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

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

<pre>
@interface OBJCache : NSObject

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

@end
</pre>

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

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

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

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

總結(jié)

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

相關(guān)鏈接

《subclassing》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末媒抠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子咏花,更是在濱河造成了極大的恐慌趴生,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迟螺,死亡現(xiàn)場離奇詭異冲秽,居然都是意外死亡,警方通過查閱死者的電腦和手機矩父,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門锉桑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窍株,你說我怎么就攤上這事民轴」ツ” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵后裸,是天一觀的道長瑰钮。 經(jīng)常有香客問我,道長微驶,這世上最難降的妖魔是什么浪谴? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮因苹,結(jié)果婚禮上苟耻,老公的妹妹穿的比我還像新娘。我一直安慰自己扶檐,他們只是感情好凶杖,可當(dāng)我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著款筑,像睡著了一般智蝠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上奈梳,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天杈湾,我揣著相機與錄音,去河邊找鬼颈嚼。 笑死毛秘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的阻课。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼艰匙,長吁一口氣:“原來是場噩夢啊……” “哼限煞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起员凝,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤署驻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后健霹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旺上,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年糖埋,在試婚紗的時候發(fā)現(xiàn)自己被綠了宣吱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞳别,死狀恐怖征候,靈堂內(nèi)的尸體忽然破棺而出杭攻,到底是詐尸還是另有隱情,我是刑警寧澤疤坝,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布兆解,位于F島的核電站,受9級特大地震影響跑揉,放射性物質(zhì)發(fā)生泄漏锅睛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一历谍、第九天 我趴在偏房一處隱蔽的房頂上張望现拒。 院中可真熱鬧,春花似錦扮饶、人聲如沸具练。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扛点。三九已至,卻和暖如春岂丘,著一層夾襖步出監(jiān)牢的瞬間陵究,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工奥帘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铜邮,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓寨蹋,卻偏偏與公主長得像松蒜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子已旧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內(nèi)容