接口是一系列可調用方法的集合。何為接口編程?接口編程是指當寫一個函數或一個方法時,我們應該更加關注具體的接口,而不是實現類。具體理解可以參考這篇文章
在OC中,接口又可以理解為Protocol,面向接口編程又可以理解為面向Protocol編程,或者面向協議編程。在Swift中,蘋果大幅強化了 Protocol 在這門語言中的地位,整個 Swift 標准庫也是基於 Protocol 來設計的,有興趣的童鞋可以看看這篇文章。面向接口編程正逐步成為程序開發的主流思想
在實際開發中,大多數朋友都比較熟悉對象編程,比如,使用ASIHttpRequest執行網絡請求
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; [request setDidFinishSelector:@selector(requestDone:)]; [request setDidFailSelector:@selector(requestWrong:)]; [request startAsynchronous];
request是請求對象,當發起請求時,調用者需要知道給對象賦哪些屬性或者調用對象哪些方法。然而,使用AFNetworking請求方式卻不盡相同
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; [manager GET:@"www.olinone.com" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { NSLog(@"好網站,贊一個!"); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { //to do }];
同是請求對象,使用AFNetworking發起請求時,調用者可以不需要關心它有哪些屬性,只有接口無法滿足需求時才需要了解相關屬性的定義。兩種設計思路完全不同,當然,此處並不是想表明孰優孰劣,只是想通過兩種截然不同的請求方式引出本文的思想——面向接口編程(或者面向協議編程)
接口比屬性直觀
在對象編程中,定義一個對象時,往往需要為其定義各種屬性。比如,ReactiveCocoa中RACSubscriber對象定義如下
@interface RACSubscriber () @property (nonatomic, copy) void (^next)(id value); @property (nonatomic, copy) void (^error)(NSError *error); @property (nonatomic, copy) void (^completed)(void); @end
參考AFNetworking的思想,以接口的形式提供訪問入口
@interface RACSubscriber + (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed; @end
通過接口的定義,調用者可以忽略對象的屬性,聚焦於其提供的接口和功能上。程序猿在首次接觸陌生的某個對象時,接口往往比屬性更加直觀明了,抽象接口往往比定義屬性更能描述想做的事情
接口依賴
設計一個APIService對象
@interface ApiService : NSObject @property (nonatomic, strong) NSURL *url; @property (nonatomic, strong) NSDictionary *param; - (void)execNetRequest; @end
正常發起Service請求時,調用者需要直接依賴該對象,起不到解耦的目的。當業務變動需要重構該對象時,所有引用該對象的地方都需要改動。如何做到既能滿足業務又能兼容變化?抽象接口也許是一個不錯的選擇,以接口依賴的方式取代對象依賴,改造代碼如下
@protocol ApiServiceProtocol - (void)requestNetWithUrl:(NSURL *)url Param:(NSDictionary *)param; @end @interface NSObject (ApiServiceProtocol) @end @implementation NSObject (ApiServiceProtocol) - (void)requestNetWithUrl:(NSURL *)url Param:(NSDictionary *)param { ApiService *apiSrevice = [ApiService new]; apiSrevice.url = url; apiSrevice.param = param; [apiSrevice execNetRequest]; } @end
通過接口的定義,調用者可以不再關心ApiService對象,也無需了解其有哪些屬性。即使需要重構替換新的對象,調用邏輯也不受任何影響。調用接口往往比訪問對象屬性更加穩定可靠
抽象對象
定義ApiServiceProtocol可以隱藏ApiService對象,但是受限於ApiService對象的存在,業務需求發生變化時,仍然需要修改ApiService邏輯代碼。如何實現在不修改已有ApiService業務代碼的條件下滿足新的業務需求?
參考Swift抽象協議的設計理念,可以使用Protocol抽象對象,畢竟調用者也不關心具體實現類。Protocol可以定義方法,可是屬性的問題怎麼解決?此時,裝飾器模式也許正好可以解決該問題,讓我們試著繼續改造ApiService
@protocol ApiService // private functions @end @interface ApiServicePassthrough : NSObject @property (nonatomic, strong) NSURL *url; @property (nonatomic, strong) NSDictionary *param; - (instancetype)initWithApiService:(id)apiService; - (void)execNetRequest; @end
@interface ApiServicePassthrough () @property (nonatomic, strong) id apiService; @end @implementation ApiServicePassthrough - (instancetype)initWithApiService:(id)apiService { if (self = [super init]) { self.apiService = apiService; } return self; } - (void)execNetRequest { [self.apiService requestNetWithUrl:self.url Param:self.param]; } @end
經過Protocol的改造,ApiService對象化身為ApiService接口,其不再依賴於任何對象,做到了真正的接口依賴取代對象依賴,具有更強的業務兼容性
定義一個Get請求對象
@interface GetApiService : NSObject @end @implementation GetApiService - (void)requestNetWithUrl:(NSURL *)url Param:(NSDictionary *)param { // to do } @end
請求代碼也隨之改變
@implementation NSObject (ApiServiceProtocol) - (void)requestNetWithUrl:(NSURL *)url Param:(NSDictionary *)param { id apiSrevice = [GetApiService new]; ApiServicePassthrough *apiServicePassthrough = [[ApiServicePassthrough alloc] initWithApiService:apiSrevice]; apiServicePassthrough.url = url; apiServicePassthrough.param = param; [apiServicePassthrough execNetRequest]; } @end
對象可以繼承對象,Protocol也可以繼承Protocol,並且可以繼承多個Protocol,Protocol具有更強的靈活性。某一天,業務需求變更需要用到新的Post請求時,可以不用修改 GetApiService一行代碼,定義一個新的 PostApiService實現Post請求即可,避免了對象裡面出現過多的if-else代碼,也保證了代碼的整潔性
依賴注入
文章寫到這裡,細心的童鞋可能已經發現問題——GetApiService依然是以對象依賴的形式存在。如何解決這個問題?沒錯,那就是依賴注入!
依賴注入是什麼?借用博客裡面的一句話,其最大的特點就是:幫助我們開發出松散耦合、可維護、可測試的代碼和程序。這條原則的做法是大家熟知的面向接口,或者說是面向抽象編程。 objc上這篇文章介紹的不錯, 有興趣的童鞋也可以看看,在此就不再累述
基於依賴注入objection開源庫的基礎上繼續改造ApiService
@implementation NSObject (ApiServiceProtocol) - (void)requestNetWithUrl:(NSURL *)url Param:(NSDictionary *)param { id apiSrevice = [[JSObjection createInjector] getObject:[GetApiService class]]; ApiServicePassthrough *apiServicePassthrough = [[ApiServicePassthrough alloc] initWithApiService:apiSrevice]; apiServicePassthrough.url = url; apiServicePassthrough.param = param; [apiServicePassthrough execNetRequest]; } @end
調用者關心請求接口,實現者關心需要實現的接口,各司其職,互不干涉
接口和實現分離的設計適用於團隊協作開發,實現了系統的松散耦合,便於以後升級擴展。當然,接口編程對開發人員的要求也比較高,需要提前定義好接口,接口一變,全部亂套,這就是所謂的設計比實現難,但是設計接口的人工資都高啊!!!
後記:你可以在github找到我,也可以通過微博聯系我,感謝你的來訪!