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

接口

接口是一系列可調用方法的集合

何爲接口編程?

接口編程是指當寫一個函數或一個方法時,我們應該更加關注具體的接口,而不是實現類

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進行了解耦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章