接口
接口是一系列可調用方法的集合
何爲接口編程?
接口編程是指當寫一個函數或一個方法時,我們應該更加關注具體的接口,而不是實現類
在OC中,接口又可以理解爲協議,面向接口編程又可以理解爲面向協議編程。在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
}];
調用者可以不需要關心它有哪些屬性,只有接口無法滿足需求時才需要了解相關屬性的定義
接口編程的優點
1. 接口比屬性直觀
在對象編程中,定義一個對象時,往往需要爲其定義各種屬性。比如,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
用面向接口編程
@interface RACSubscriber
+ (instancetype)subscriberWithNext:(void (^)(id x))next
error:(void (^)(NSError *error))error
completed:(void (^)(void))completed;
@end
通過接口的定義,調用者可以忽略對象的屬性,聚焦於其提供的接口和功能上。程序猿在首次接觸陌生的某個對象時,接口往往比屬性更加直觀明瞭,抽象接口往往比定義屬性更能描述想做的事情
2. 接口依賴
設計一個APIService對象
面向對象編程
@interface ApiService : NSObject
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, strong) NSDictionary *param;
- (void)execNetRequest;
@end
正常發起Service請求時,調用者需要直接依賴該對象,起不到解耦的目的。當業務變動需要重構該對象時,所有引用該對象的地方都需要改動。如何做到既能滿足業務又能兼容變化?抽象接口也許是一個不錯的選擇,以接口依賴的方式取代對象依賴,改造代碼如下
協議文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol ApiServiceProtocol <NSObject>
- (void)requestNetWithUrl:(NSURL *)url Param:(NSDictionary *)param;
@end
.h文件
#import <Foundation/Foundation.h>
#import "ApiServiceProtocol.h"
NS_ASSUME_NONNULL_BEGIN
@interface ApiService : NSObject<ApiServiceProtocol>
@end
.m文件
#import "ApiService.h"
@interface ApiService()
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, strong) NSDictionary *param;
- (void)execNetRequest;
@end
@implementation ApiService
- (void)requestNetWithUrl:(NSURL *)url Param:(NSDictionary *)param {
ApiService *apiSrevice = [ApiService new];
apiSrevice.url = url;
apiSrevice.param = param;
[apiSrevice execNetRequest];
}
- (void)execNetRequest
{
}
@end
通過接口的定義,調用者可以不再關心ApiService對象,也無需瞭解其有哪些屬性。即使需要重構替換新的對象,調用邏輯也不受任何影響。調用接口往往比訪問對象屬性更加穩定可靠
3. 抽象對象
定義ApiServiceProtocol可以隱藏ApiService對象,但是受限於ApiService對象的存在,業務需求發生變化時,仍然需要修改ApiService邏輯代碼。如何實現在不修改已有ApiService業務代碼的條件下滿足新的業務需求?
參考Swift抽象協議的設計理念,可以使用Protocol抽象對象,畢竟調用者也不關心具體實現類。Protocol可以定義方法,可是屬性的問題怎麼解決?此時,裝飾器模式也許正好可以解決該問題,讓我們試着繼續改造ApiService
裝飾器模式:允許向一個現有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬於結構型模式,它是作爲現有的類的一個包裝。
ApiServicePassthrough .h 文件
#import <Foundation/Foundation.h>
@protocol ApiServiceProtocol;
NS_ASSUME_NONNULL_BEGIN
@interface ApiServicePassthrough : NSObject
@property (nonatomic, strong) id<ApiServiceProtocol> apiService;
@end
NS_ASSUME_NONNULL_END
ApiServicePassthrough.m 文件
import "ApiServicePassthrough.h"
#import "ApiServiceProtocol.h"
@interface ApiServicePassthrough()
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, strong) NSDictionary *param;
- (instancetype)initWithApiService:(id<ApiServiceProtocol>)apiService;
- (void)execNetRequest;
@end
@implementation ApiServicePassthrough
- (instancetype)initWithApiService:(id<ApiServiceProtocol>)apiService {
if (self = [super init]) {
self.apiService = apiService;
}
return self;
}
- (void)execNetRequest {
[self.apiService requestNetWithUrl:self.url Param:self.param];
}
經過Protocol的改造,ApiService對象化身爲ApiService接口,其不再依賴於任何對象,做到了真正的接口依賴取代對象依賴,具有更強的業務兼容性
4.依賴注入
非自己主動初始化依賴,而通過外部來傳入依賴的方式,我們就稱爲依賴注入。
調用者關心請求接口,實現者關心需要實現的接口,各司其職,互不干涉
賴注入主要有兩個好處:
(1). 解耦,將依賴之間解耦。
(2). 因爲已經解耦,所以方便做單元測試,尤其是 Mock 測試。
- (void)requestNetWithUrl:(NSURL *)url Param:(NSDictionary *)param {
id<ApiServiceProtocol> apiSrevice = [ApiServiceFactory getApiService];
ApiServicePassthrough *apiServicePassthrough = [[ApiServicePassthrough alloc] initWithApiService:apiSrevice];
apiServicePassthrough.url = url;
apiServicePassthrough.param = param;
[apiServicePassthrough execNetRequest];
}
通過工廠模式來獲得對象
ApiServiceFactory.m 文件
+ (id<ApiServiceProtocol>)getApiService
{
ApiService *server = [[ApiService alloc]init];
return server;
}
如果一個類A 的功能實現需要藉助於類B,那麼就稱類B是類A的依賴,如果在類A的內部去實例化類B,那麼兩者之間會出現較高的耦合,一旦類B出現了問題,類A也需要進行改造,如果這樣的情況較多,每個類之間都有很多依賴,那麼就會出現牽一髮而動全身的情況,程序會極難維護,並且很容易出現問題。要解決這個問題,就要把A類對B類的控制權抽離出來,交給一個第三方去做,把控制權反轉給第三方,就稱作控制反轉(IOC Inversion Of Control)。控制反轉是一種思想,是能夠解決問題的一種可能的結果,而依賴注入(Dependency Injection)就是其最典型的實現方法。由第三方(我們稱作IOC容器)來控制依賴,把他通過構造函數、屬性或者工廠模式等方法,注入到類A內,這樣就極大程度的對類A和類B進行了解耦。