本篇文章聊一下传参的问题,这是基本每一个快速发展的项目都会遇到的问题,这里聊一下我自己在开发过程中想到的解决方案以及踩得坑。

如果可以前置把问题解决,会静默提升团队开发的效率。

示例github:解决参数爆炸问题

一、问题描述

(一)解决参数爆炸问题

首先先描述下我们要解决的问题:随着接口参数的不断增加,一个业务加一个参数,会导致接口的传参爆炸,且新调用接口的同学不清楚旧参数的含义。比如:

1
2
3
4
5
6
7
8
9
10
11
- (instancetype)initWithParam1:(int)param1
param2:(int)param2
param3:(int)param3
param4:(int)param4
param5:(int)param5
param6:(int)param6 {
if (self = [super init]) {

}
return self;
}

A业务 可能只需要传 param1、param2、param4 ,而B业务可能只需要传 param1、param2、param5。

所以第一个优化的想法出来了:参数业务隔离,按业务拆分接口,收敛基类接口,优化的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 方法A
- (instancetype)initWithParam1:(int)param1
param2:(int)param2
param4:(int)param4{
if (self = [super init]) {

}
return self;
}

// 方法B
- (instancetype)initWithParam1:(int)param1
param2:(int)param2
param5:(int)param5{
if (self = [super init]) {

}
return self;
}

// 方法C
- (instancetype)initWithParam1:(int)param1
param2:(int)param2
param5:(int)param5
param6:(int)param6{
if (self = [super init]) {

}
return self;
}

这种接口按业务隔离的方式我们实行了一段时间,刚开始还是比较顺利的,但当接入的业务越来越多,我们发现接口已经开始相互套娃了,比如 方法B调用了方法C ,当我们需要新增一个参数时,需要改动N个业务的初始化方法。

且因为自动补全的缘故,当参数越来越多时,很可能有同学写 init 方法就变成了 方法B调用方法C,然后方法C又调用了方法B,出现死循环(真实发生过)。

所以业务隔离并不是解决参数爆炸的好方法,还会引入套娃问题,那能不能只暴露一个接口,让业务层自己决定传什么呢?

(二)解决套娃问题

最简单的方式是接口只暴露一个参数:NSDictionary,外界要传什么元素,就塞到 NSDictionary 就可以了。

1
2
3
4
5
6
- (instancetype)initWithDic:(NSMutableDictionary *)dic {
if (self = [super init]) {

}
return self;
}

这种方式肯定是不行的,传参一定不要使用 NSDictionary ,这种传参的方式就是在偷懒,如果不在init方法里打断点,开发者是根本不知道接口要通过 NSDictionary 传过来什么参数,相当于把接收参数的风险转嫁到了接收方,且传参的业务方也不清楚应该按什么样的规格传参,所以这种方式一定要pass掉,至少应该用Model来进行传参。

(三)解决隐藏参数问题

我们为传参新建一个model,这里起名BNParamsModel

然后我们将要传的参数都塞到这个 Model 里:

1
2
3
4
5
6
7
8
9
10
@interface BNParamsModel : NSObject

@property (nonatomic, assign) int param1;
@property (nonatomic, assign) int param2;
@property (nonatomic, assign) int param3;
@property (nonatomic, assign) int param4;
@property (nonatomic, assign) int param5;
@property (nonatomic, assign) int param6;

@end

然后在初始化方法中传入Model参数:

1
2
3
4
5
6
- (instancetype)initWithParamModel:(BNParamsModel *)model {
if (self = [super init]) {

}
return self;
}

这样之后添加的参数都放到Model中,就不会造成接口参数爆炸的问题,同时也让传参可读。

但这样还是会出现一个问题:不同业务添加自己业务的字段到 Model 中,但 A业务 可能只需要传 param1、param2、param4 ,而B业务可能只需要传 param1、param2、param5。

新接手这个接口的同学不知道哪些字段是必填(required)的,哪些字段是和业务相关可选(optional)的,这时就要求我们对 ParamsModel 进行一个改造。

(四)解决漏传参数问题

我们将参数标识为 required 和 optional ,提供 _init 方法,通过代码强制约束 init 方法必须传入 required 的参数,BNParamsModel代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
BNParamsModel.h

@interface BNParamsModel : NSObject

// required
@property (nonatomic, assign, readonly) int param1;
@property (nonatomic, assign, readonly) int param2;

// optional
@property (nonatomic, assign, readonly) int param3;
@property (nonatomic, assign, readonly) int param4;
@property (nonatomic, assign, readonly) int param5;
@property (nonatomic, assign, readonly) int param6;

// 业务自己构建初始化方法,不要使用 init\new 进行构建
+ (instancetype)new DISPATCH_UNAVAILABLE_MSG("Use initWith BUSINESS");
- (instancetype)init DISPATCH_UNAVAILABLE_MSG("Use initWith BUSINESS");

// 业务A
- (instancetype)initWithParam1:(int)param1
param2:(int)param2
param4:(int)param4;

// 业务B
- (instancetype)initWithParam1:(int)param1
param2:(int)param2
param5:(int)param5;

@end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
BNParamsModel.m

@interface BNParamsModel ()

// required
@property (nonatomic, assign) int param1;
@property (nonatomic, assign) int param2;

// optional
@property (nonatomic, assign) int param3;
@property (nonatomic, assign) int param4;
@property (nonatomic, assign) int param5;
@property (nonatomic, assign) int param6;

@end

@implementation BNParamsModel

- (instancetype)_initWithParam1:(int)param1
param2:(int)param2 {
if (self = [super init]) {
_param1 = param1;
_param2 = param2;
}
return self;
}

// 业务A
- (instancetype)initWithParam1:(int)param1
param2:(int)param2
param4:(int)param4 {
self = [self _initWithParam1:param1 param2:param2];
if (self) {
_param4 = param4;
}
return self;
}

// 业务B
- (instancetype)initWithParam1:(int)param1
param2:(int)param2
param5:(int)param5 {
self = [self _initWithParam1:param1 param2:param2];
if (self) {
_param5 = param5;
}
return self;
}

@end

示例代码在:github-解决参数爆炸问题

二、解决方案

(一)优点

这种处理方式不仅解决了参数爆炸问题,而且还通过init方式隔离业务,明确了调用业务的场景。

当有新业务接入时,就写自己的init方法,让我们知道当前有多少业务接入。

(二)缺点

唯一的缺点就是工作量的问题,你不必为每个传参接口都构建对应的 ParamsModel ,只有当你评估该接口未来会有各种业务接入,会新增各种参数时,才建议你构建接口的 ParamsModel。