這篇文章跟我以往的文章有點不一樣抑堡。它主要是一些思想與模式的匯集站玄,而不是一篇指南按摘。下面我所寫的模式幾乎全都來之不易包券,都是我犯了錯之后才學到的。我并不認為自己是子類方面的權威炫贤,但我確實想把我學到的一些東西分享出來溅固。別把本文當做權威指南,它只是一些例子的匯集兰珍。
在被問到 OOP(面向對象編程)的時候侍郭,Alan Kay(OOP 的發(fā)明人)寫到:
它跟類無關,但跟消息有關。
然而亮元,很多人的關注點仍然還在類層次上猛计。在本文中,我們會看幾個我們可能會把注意力放在創(chuàng)建復雜的類結構上的例子爆捞,并給出更有用的替代方案奉瘤。根據(jù)經(jīng)驗,這樣會讓代碼更簡單煮甥,更易維護盗温。
何時用子類
首先,我們討論幾種使用子類比較合適的場景成肘。
如果你要寫一個自定義布局的 UITableViewCell 卖局,那就創(chuàng)建一個子類。這同樣適用于幾乎每個視圖艇劫。一旦你開始布局吼驶,把這塊代碼放入子類就更合理一些,不光代碼得到了更好的封裝店煞,你也能得到一個可在工程之間重用的組件蟹演。
假設你的代碼是針對多平臺多版本的,并且你需要針對每個平臺每個版本寫一些代碼顷蟀。這時候更合理的做法可能是創(chuàng)建一個 OBJDevice 類酒请,讓一些子類如 OBJIPhoneDevice 和 OBJIPadDevice ,甚至更深層的子類如 OBJIPhone5Device 來繼承鸣个,并讓這些子類重寫特定的方法羞反。例如,你的 OBJDevice 類可能包含了函數(shù) applyRoundedCornersToView:withRadius 囤萤,它有一個默認的實現(xiàn)昼窗,但是也能被特定的子類重寫。
另一個子類化可能很有用的場景是模型對象(model object)涛舍。絕大多數(shù)情況下澄惊,我的模型對象繼承自一個實現(xiàn)了 isEqual: 、 hash 富雅、 copyWithZone: 和 description 等方法的類掸驱。這些方法只被實現(xiàn)一次,并且迭代循環(huán)遍歷所有屬性没佑,所以極不容易出錯毕贼。(如果你也想找一個這樣的基類,可以考慮使用 Mantle 蛤奢,它就是這么做的鬼癣,并且做得更多陶贼。)
何時不使用子類
在以往工作過的很多工程中,我見到過很多繼承層次很深的子類扣溺。當我也這么干的時候骇窍,總會感到內疚。除非繼承的層次非常淺锥余,否則你會很快發(fā)現(xiàn)它的局限性腹纳。
幸運的是,如果你發(fā)現(xiàn)自己正在使用深層次的繼承驱犹,還有很多替代方案可選嘲恍。在下面的章節(jié)中,我們會逐個進行更詳細地描述雄驹。如果你的子類只是使用相同的接口佃牛,協(xié)議會是個非常好的替代方案。如果你知道某個對象需要大量的修改医舆,你可能會使用代理來動態(tài)改變和配置它俘侠。當你想給已有對象增加一些簡單功能時,類別可能是個選擇蔬将。當你有一堆重寫了相同方法的子類時爷速,你可以使用配置對象(configuration object)來代替。最后霞怀,當你想重用某些功能時惫东,組合多個對象而不是擴展它們可能會更好。
替代方案:協(xié)議(Protocols)
很多時候毙石,使用子類的原因是你想保證某個對象可以響應某些消息廉沮。假設在 app 里你有一個播放器對象,它可以播放視頻⌒炀兀現(xiàn)在你想添加對 YouTube 的支持滞时,使用相同的接口,但是具體實現(xiàn)不同滤灯。你可以使像這樣用子類來實現(xiàn):
@class Player : NSObject
- (void)play;
- (void)pause;
@end
@class YouTubePlayer : Player
@end
事實上可能這兩個類并沒有太多共用的代碼坪稽,它們只不過具有相同的接口。如果這樣的話力喷,使用協(xié)議可能會是更好的方案刽漂⊙菅担可以這樣用協(xié)議來寫你的代碼:
@protocol VideoPlayer <NSObject>
- (void)play;
- (void)pause;
@end
@class Player : NSObject <VideoPlayer>
@end
@class YouTubePlayer : NSObject <VideoPlayer>
@end
這樣弟孟,YouTubePlayer 類就不必知道 Player 類的內部實現(xiàn)了。
替代方案:代理(Delegation)
再一次假設你有一個像上面例子中的 Player 類⊙颍現(xiàn)在拂募,你想在開始播放的時候在某個地方執(zhí)行一個自定義的函數(shù)庭猩。這么做相對容易一些:創(chuàng)建一個自定義的子類,重寫 play 方法陈症,調用 [super play ]蔼水,然后開始做你自定義的工作。這么做是一種方法录肯。另外一種方法是趴腋,改動你的 Player 對象,然后給它設置一個代理论咏。如下:
@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 類的任何使用者都可以僅僅實現(xiàn)這個代理協(xié)議蠢护,而不用繼承該該類, Player 類也能夠保持通用性养涮。這是個強大有效的技術葵硕,蘋果在自己的框架里大量地使用它。你想想像 UITextField 這樣的類贯吓,還有 NSLayoutManager懈凹。有時候你還會想把幾個不同的方法打包分組到幾個單獨的協(xié)議里,比如 UITableView—— 它不僅有一個代理(delegate)宣决,還有一個數(shù)據(jù)源(dataSource)蘸劈。
替代方案:類別(Categories)
有時候,你可能會想給一個對象增加一點點額外的功能尊沸。比如你想給 NSArray 增加一個方法 arrayByRemovingFirstObject威沫。不用子類,你可以把這個函數(shù)放到一個類別里洼专。像這樣:
@interface NSArray (OBJExtras)
- (void)obj_arrayByRemovingFirstObject;
@end
在用類別擴展一個不是你自己的類的時候棒掠,在方法前添加前綴是個比較好的習慣做法。如果不這么做屁商,有可能別人也用類別對此類添加了相同名字的函數(shù)烟很。那時候程序的行為可能跟你想要的并不一樣,未預期的事情可能會發(fā)生蜡镶。
使用類別還有另外一個風險雾袱,那就是,到最后你可能會使用一大堆的類別官还,連你自己都會失去對代碼全局的認識芹橡。假如那樣的話,創(chuàng)建自定義的類可能更簡單一些望伦。
替代方案:配置對象(Configuration Objects)
在我經(jīng)常會犯的錯誤中(現(xiàn)在很快就能發(fā)現(xiàn)了)林说,其中有一條是:使用一個含有幾個抽象方法的類并讓很多子類來重寫某個方法煎殷。例如,在一個幻燈片應用里腿箩,你有一個主題類 Theme 豪直,它有幾個屬性,比如 backgroundColor 和 font 珠移,還有一些在一張幻燈片上如何布局的邏輯函數(shù)弓乙。
然后,對每種主題钧惧,你都創(chuàng)建一個 Theme 的子類唆貌,重寫某個函數(shù)(例如 setup )并配置其屬性。直接使用父類對此做不了什么事垢乙。在這種情況下锨咙,你可以使用配置對象來讓代碼更簡單些。你可以把共有的邏輯(比如幻燈片布局)放在 Theme 類中追逮,把屬性的配置放到較簡單的對象中酪刀,這些對象中只含有這些屬性。
例如钮孵,類 ThemeConfiguration 具有 backgroundColor 和 font 屬性骂倘,而類 Theme 在其初始化函數(shù)中獲取一個配置類 ThemeConfiguration 的值。
替代方案:組合
組合是代替子類化的最強大有效的方案巴席。如果你想重用已有代碼而不想共享同樣的接口历涝,組合就是你的首選武器。例如漾唉,假設你要設計一個緩存類:
@interface OBJCache : NSObject
- (void)cacheValue:(id)value forKey:(NSString *)key;
- (void)removeCachedValueForKey:(NSString *)key;
@end
簡單點的做法是直接繼承 NSDictionary荧库,通過調用字典的函數(shù)來實現(xiàn)上面的兩個方法。
@interface OBJCache : NSDictionary
但是這么做有幾個弊端赵刑。它本來是應該被詳細實現(xiàn)的分衫,但只是通過字典來實現(xiàn)。現(xiàn)在般此,在任何需要一個 NSDictionary 參數(shù)的時候蚪战,你可以直接提供一個 OBJCache 值。但如果你想把它轉為其它完全不同的東西(例如你自己的庫)铐懊,你就可能需要重構很多代碼了邀桑。
更好的方式是,將這個字典存在一個私有屬性(或者實例變量)中科乎,對外僅僅暴露這兩個 cache 方法”诨現(xiàn)在,當你有了更深入想法的時候喜喂,你可以在靈活地修改其實現(xiàn)瓤摧,而該類的使用者們不用進行重構。
此文章原文鏈接自己的個人博客: www.koalaliu.com ,因簡書平臺規(guī)范性以及用戶量玉吁,搬至簡書照弥。