你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 接口編程那些事(或者面向協議編程)

接口編程那些事(或者面向協議編程)

編輯:IOS開發基礎

1.jpg

接口是一系列可調用方法的集合。何為接口編程?接口編程是指當寫一個函數或一個方法時,我們應該更加關注具體的接口,而不是實現類。具體理解可以參考這篇文章

在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找到我,也可以通過微博聯系我,感謝你的來訪!

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved