在 Objective-C 的世界里面經(jīng)常錯過的一個東西是抽象接口。接口(interface)這個詞通常指一個類的 .h
文件,但是它在 Java 程序員眼里有另外的含義: 一系列不依賴具體實現(xiàn)的方法的定義。
在 Objective-C 里是通過 protocol 來實現(xiàn)抽象接口的。因為歷史原因,protocol (作為 Java 接口使用)并沒有在 Objective-C 社區(qū)里面廣泛使用。一個主要原因是大多數(shù)的 Apple 開發(fā)的代碼沒有包含它,而幾乎所有的開發(fā)者都是遵從 Apple 的模式以及指南的。Apple 幾乎只是在委托模式下使用 protocol。
但是抽象接口的概念很強大,它計算機科學(xué)的歷史中就有起源,沒有理由不在 Objective-C 中使用。
我們會解釋 protocol 的強大力量(用作抽象接口),用具體的例子來解釋:把非常糟糕的設(shè)計的架構(gòu)改造為一個良好的可復(fù)用的代碼。
這個例子是在實現(xiàn)一個 RSS 訂閱的閱讀器(它可是經(jīng)常在技術(shù)面試中作為一個測試題呢)。
要求很簡單明了:把一個遠程的 RSS 訂閱展示在一個 tableview 中。
一個幼稚的方法是創(chuàng)建一個 UITableViewController
的子類,并且把所有的檢索訂閱數(shù)據(jù),解析以及展示的邏輯放在一起,或者說是一個 MVC (Massive View Controller)。這可以跑起來,但是它的設(shè)計非常糟糕,不過它足夠過一些要求不高的面試了。
A minimal step forward would be to follow the Single Responsibility Principle and create at least 2 components to do the different tasks:
最小的步驟是遵從單一功能原則,創(chuàng)建至少兩個組成部分來完成這個任務(wù):
這些類的接口可以是這樣的:
@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)建有意義的數(shù)據(jù)),ZOCTableViewController
會用這個 parser 來進行初始化。 我們希望它顯示 parser 接受到的指并且我們用下面的 protocol 實現(xiàn)委托:
@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
用合適的 protocol 來來處理 RSS 非常完美。view controller 會遵從它的公開的接口:
@interface ZOCTableViewController : UITableViewController <ZOCFeedParserDelegate>
最后創(chuàng)建的代碼是這樣子的:
NSURL *feedURL = [NSURL URLWithString:@"http://bbc.co.uk/feed.rss"];
ZOCFeedParser *feedParser = [[ZOCFeedParser alloc] initWithURL:feedURL];
ZOCTableViewController *tableViewController = [[ZOCTableViewController alloc] initWithFeedParser:feedParser];
feedParser.delegate = tableViewController;
到目前你可能覺得你的代碼還是不錯的,但是有多少代碼是可以有效復(fù)用的呢?view controller 只能處理 ZOCFeedParser
類型的對象: 從這點來看我們只是把代碼分離成了兩個組成部分,而沒有做任何其他有價值的事情。
view controller 的職責(zé)應(yīng)該是“從上顯示一些內(nèi)容”,但是如果我們只允許傳遞ZOCFeedParser
的話就不是這樣的了。這就表現(xiàn)了需要傳遞給 View controller 一個更泛型的對象的需求。
We modify our feed parser introducing the ZOCFeedParserProtocol
protocol (in the ZOCFeedParserProtocol.h file where also ZOCFeedParserDelegate
will be).
我們使用 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 現(xiàn)在處理響應(yīng)我們新的 protocol 而且 ZOCFeedParser 的接口文件更加精煉了:
@interface ZOCFeedParser : NSObject <ZOCFeedParserProtocol>
- (id)initWithURL:(NSURL *)url;
@end
因為 ZOCFeedParser
實現(xiàn)了 ZOCFeedParserProtocol
,它需要實現(xiàn)所有需要的方法。
從這點來看 view controller 可以接受任何實現(xiàn)這個新的 protocol 的對象,確保所有的對象會響應(yīng)從 start
和 stop
的方法,而且它會通過 delegate 的屬性來提供信息。所有的 view controller 只需要知道相關(guān)對象并且不需要知道實現(xiàn)的細節(jié)。
@interface ZOCTableViewController : UITableViewController <ZOCFeedParserDelegate>
- (instancetype)initWithFeedParser:(id<ZOCFeedParserProtocol>)feedParser;
@end
上面的代碼片段的改變看起來不多,但是有了一個巨大的提升。view controller 是面向一個協(xié)議而不是具體的實現(xiàn)的。這帶來了以下的優(yōu)點:
ZOCFeedParser
和 ZOCFeedParserDelegate
可以被其他組成部分復(fù)用ZOCViewController
(UI邏輯部分)可以被復(fù)用當(dāng)實現(xiàn)一個 protocol 你總應(yīng)該堅持 里氏替換原則。這個原則讓你應(yīng)該取代任意接口(也就是Objective-C里的的"protocol")實現(xiàn),而不用改變客戶端或者相關(guān)實現(xiàn)。
此外這也意味著你的 protocol 不應(yīng)該關(guān)注實現(xiàn)類的細節(jié),更加認真地設(shè)計你的 protocol 的抽象表述的時候,需要注意它和底層實現(xiàn)是不相干的,協(xié)議是暴露給使用者的抽象概念。
任何可以在未來復(fù)用的設(shè)計意味著可以提高代碼質(zhì)量,同時也是程序員的目標。是否這樣設(shè)計代碼,就是大師和菜鳥的區(qū)別。
最后的代碼可以在這找到。here.
更多建議: