接口编程那些事(或者面向协议编程)

接口

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

何为接口编程?

接口编程是指当写一个函数或一个方法时,我们应该更加关注具体的接口,而不是实现类

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

发布了128 篇原创文章 · 获赞 106 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/Z1591090/article/details/103628010