在 Objective-C 的世界里面經常錯過的一個東西是抽象接口。接口(interface)這個詞通常指一個類的 .h文件,但是它在 Java 程序員眼里有另外的含義: 一系列不依賴具體實現的方法的定義仁连。(譯者注:在OC中唠叛,類的接口對應在.m文件中都會有具體的實現婴梧,但Java中接口更接近于OC中的抽象接口或者說協議(protocol))在 Objective-C 里是通過 protocol 來實現抽象接口的矗积。因為歷史原因蔓倍,protocol (使用方法類似java的接口)并沒有大量地在Objective-C的代碼中使用也沒有在社區(qū)中普及(指的是那種像Java程序員使用接口那樣來使用protocol的方式)悬钳。一個主要原因是大多數的 Apple 開發(fā)的代碼沒有采用這種的方式,而幾乎所有的開發(fā)者都是遵從 Apple 的模式以及指南偶翅。Apple 幾乎只是在委托模式下使用 protocol默勾。但是抽象接口的概念很強大,在計算機科學的歷史中頗有淵源聚谁,沒有理由不在 Objective-C 中使用母剥。這里通過一個具體的例子來解釋 protocol 的強大力量(用作抽象接口):把非常糟糕的設計的架構改造為一個良好的可復用的代碼。這個例子是在實現一個 RSS 閱讀器(它可是經常在技術面試中作為一個測試題呢)形导。要求很簡單:在TableView中展示一個遠程的RSS訂閱环疼。一個幼稚的方法是創(chuàng)建一個 UITableViewController的子類,并且把所有的檢索訂閱數據朵耕,解析以及展示的邏輯放在一起炫隶,或者說是一個 MVC (Massive View Controller)。這可以跑起來阎曹,但是它的設計非常糟糕伪阶,不過它足夠過一些要求不高的面試了。最小的步驟是遵從單一功能原則处嫌,創(chuàng)建至少兩個組成部分來完成這個任務:一個 feed 解析器來解析搜集到的結果一個 feed 閱讀器來顯示結果
這些類的接口可以是這樣的:
@interface ZOCFeedParser : NSObject
@property (nonatomic, weak) id <ZOCFeedParserDelegate> delegate;
@property (nonatomic, strong) NSURL *url;
- (id)initWithURL:(NSURL *)url;
- (BOOL)start;- (void)stop;
@end
@interface ZOCTableViewController : UITableViewController
- (instancetype)initWithFeedParser:(ZOCFeedParser *)feedParser;
@end
ZOCFeedParser用 NSURL 進行初始化栅贴,來獲取 RSS 訂閱(在這之下可能會使用 NSXMLParser 和 NSXMLParserDelegate 創(chuàng)建有意義的數據),ZOCTableViewController會用這個 parser 來進行初始化锰霜。 我們希望它顯示 parser 接受到的值并且我們用下面的 protocol 實現委托:
@protocol ZOCFeedParserDelegate <NSObject>
@optional
- (void)feedParserDidStart:(ZOCFeedParser *)parser;
- (void)feedParser:(ZOCFeedParser *)parser didParseFeedInfo:(ZOCFeedInfoDTO *)info;
- (void)feedParser:(ZOCFeedParser *)parser didParseFeedItem:(ZOCFeedItemDTO *)item;
- (void)feedParserDidFinish:(ZOCFeedParser *)parser;
- (void)feedParser:(ZOCFeedParser *)parser didFailWithError:(NSError *)error;
@end
我要說筹误,這是一個處理RSS業(yè)務的完全合理而恰當的protocol。這個ViewController在Public接口中將遵循這個protocol:
@interface ZOCTableViewController : UITableViewController <ZOCFeedParserDelegate>
最后創(chuàng)建的代碼是這樣子的:
NSURL *feedURL = [NSURL URLWithString:@"http://www.bbc.co.uk/feed.rss"];
ZOCFeedParser *feedParser = [[ZOCFeedParser alloc] initWithURL:feedURL];
ZOCTableViewController *tableViewController = [[ZOCTableViewController alloc] initWithFeedParser:feedParser];
feedParser.delegate = tableViewController;
到目前你可能覺得你的代碼還是不錯的癣缅,但是有多少代碼是可以有效復用的呢厨剪?view controller 只能處理 ZOCFeedParser 類型的對象: 從這點來看我們只是把代碼分離成了兩個組成部分,而沒有做任何其他有價值的事情友存。view controller 的職責應該是“顯示某些東西提供的內容”祷膳,但是如果我們只允許傳遞ZOCFeedParser的話,就不是這樣的了屡立。這就體現了需要傳遞給 view controller 一個更泛型的對象的需求直晨。我們使用 ZOCFeedParserProtocol這個 protocol (在 ZOCFeedParserProtocol.h 文件里面,同時文件里也有ZOCFeedParserDelegate)。
@protocol ZOCFeedParserProtocol <NSObject>
@property (nonatomic, weak) id <ZOCFeedParserDelegate> delegate;
@property (nonatomic, strong) NSURL *url;
- (BOOL)start;
- (void)stop;
@end
@protocol ZOCFeedParserDelegate <NSObject>
@optional
- (void)feedParserDidStart:(id<ZOCFeedParserProtocol>)parser;
- (void)feedParser:(id<ZOCFeedParserProtocol>)parser didParseFeedInfo:(ZOCFeedInfoDTO *)info;
- (void)feedParser:(id<ZOCFeedParserProtocol>)parser didParseFeedItem:(ZOCFeedItemDTO *)item;
- (void)feedParserDidFinish:(id<ZOCFeedParserProtocol>)parser;
- (void)feedParser:(id<ZOCFeedParserProtocol>)parser didFailWithError:(NSError *)error;
@end
注意這個代理 protocol 現在處理響應我們新的 protocol勇皇, 而且 ZOCFeedParser 的接口文件更加精煉了:
@interface ZOCFeedParser : NSObject <ZOCFeedParserProtocol>
- (id)initWithURL:(NSURL *)url;
@end
因為 ZOCFeedParser實現了 ZOCFeedParserProtocol罩句,它需要實現所有的required方法。 從這點來看 viewController能接受任何遵循該協議的對象敛摘,只要確保所有的對象都會響應start和stop方法并通過delegate屬性提供信息(譯者注:因為protocol默認情況下所有的方法定義都是required的)门烂。對指定的對象而言,這就是viewController所要知道的一切,且不需要知道其實現的細節(jié)兄淫。
@interface ZOCTableViewController : UITableViewController <ZOCFeedParserDelegate>
- (instancetype)initWithFeedParser:(id<ZOCFeedParserProtocol>)feedParser;
@end
上面的代碼片段的改變看起來不多屯远,但是有了一個巨大的提升。view controller 將基于協議而不是具體的實現來工作捕虽。這帶來了以下的優(yōu)點:view controller 現在可以接收通過delegate屬性提供信息的任意對象:可以是 RSS 遠程解析器慨丐,或者本地解析器,或是一個讀取其他遠程或者本地數據的服務ZOCFeedParser和 ZOCFeedParserDelegate可以被其他組成部分復用ZOCViewController(UI邏輯部分)可以被復用測試更簡單了泄私,因為可以用 mock 對象來達到 protocol 預期的效果當實現一個protocol 你總應該堅持 里氏替換原則房揭。這個原則是:你應該可以取代任意接口(也就是Objective-C里的"protocol")實現,而不用改變客戶端或者相關實現晌端。此外崩溪,這也意味著protocol不該關心類的實現細節(jié);設計protocol的抽象表述時應非常用心斩松,并且要牢記它和它背后的實現是不相干的伶唯,真正重要的是協議(這個暴露給使用者的抽象表述)。任何在未來可復用的設計惧盹,無形當中可以提高代碼質量乳幸,這也應該一直是程序員的追求。是否這樣設計代碼钧椰,就是大師和菜鳥的區(qū)別粹断。
最后的代碼可以在這里 找到。