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