繼承
繼承從代碼復(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)先考慮組合等方式双饥。