注:本文参考 记一次网络模块的小规模重构 以及 MVVM without ReactiveCocoa
看完本文后,如果需要代码的话,请移步https://github.com/tepmnthar/WTCommand
就从这两篇文章结束的的地方说起,这两篇文章提供了一个很好的模版和思路,但仍有一些值得改进的地方。
值得改进的点<a id="orgheadline1"></a>
- 不需要将error和result暴露给下游环节,对于下游环节来说,只需要能将输入和输出回调交给指定command就行了,并不需要其来判断究竟该执行哪个回调,这个操作应该由更上层的来完成。
- 所以为了实现上面这个要求,并且进一步的分离业务逻辑层和调用请求层,需要在这中间额外加一个中间层,用来控制请求路由和控制成功失败回调。本文以网络请求为例,即在中间层使用工厂类创建所需command,并且将网络请求结果放进对应的成功或失败回调,下游的业务逻辑层无需再做判断
- 其他一些功能,诸如监视command状态变化;final回调;类似其它语言的点表达式之类。
解决和改进方案<a id="orgheadline6"></a>
目录结构<a id="orgheadline2"></a>
- WTCommand commad类
- WebCommands 工厂类
现在手上的代码<a id="orgheadline3"></a>
WTCommand
typedef enum : NSInteger {
COMMAND_IDLE,
COMMAND_PENDING,
COMMAND_SUCCESS,
COMMAND_ERROR,
COMMAND_CANCEL
} CommandStatus;
-(instancetype)initWithConsumeHandler:(WTCommandConsumeBlock)consumeHandler successHandler:(WTCommandSuccessBlock)successHandler errorHandler:(WTCommandErrorBlock)errorHandler{
self=[super init];
if (!self) return nil;
_status=COMMAND_IDLE; //unused
_consumeHandler=consumeHandler;
__weak typeof(self)weakself=self;
_successHandler=^(id result){
__strong typeof(self)strongself=weakself;
strongself.status=COMMAND_SUCCESS;
successHandler(result);
};
_errorHandler=^(id error){
__strong typeof(self)strongself=weakself;
strongself.status=COMMAND_ERROR;
errorHandler(error);
};
return self;
}
-(void)execute:(id)input{
if (_status==COMMAND_PENDING)return;
_status=COMMAND_PENDING;
_consumeHandler(input);
}
开头介绍的两篇文章读完后,代码差不多应该是这样的。创建command时设置command的请求函数和失败成功回调函数,通过execute来执行请求。后面我们会将这部分逻辑从业务逻辑层分离出来。
使用工厂类封装接口<a id="orgheadline4"></a>
改进的第一步是首先创建工厂类,通过提供参数的方式来使业务逻辑层获得command,而不是业务逻辑层直接去操作command。直接在业务逻辑层中创建command会造成代码臃肿,实际上业务逻辑层总是在调用那么几个接口,仅仅是设置的参数不一样,回调函数不一样,所以可以抽象一下,业务逻辑层仅仅只关心要执行哪个command,输入是什么,输出怎么处理,仅此而已。
而工厂类则负责根据业务逻辑层提供的参数,去装配出指定的command,请求哪个接口,以及把返回结果放进哪个回调,全部由工厂类内部完成。工厂类实际起到了路由的功能,负责request和response的收发。
创建工厂类的另外一个好处就是,代码逻辑都写在一个类里,代码很好复用。如果要换请求接口,比起修改混杂在业务逻辑中的command的做法,要容易许多。如果回调可以复用,甚至可以把回调函数也写在里面,这样业务逻辑层里代码就只有一句话,非常简洁。
这里以网络请求为例,创建WebCommand类:
WebCommand.m
定义command类型:
typedef enum {
WEB_COMMAND_NONE,
WEB_COMMAND_TEST_GET,
WEB_COMMAND_TEST_POST
}WEB_COMMANDS_TYPE;
创建对应command:
+ (WTCommand*)constructCommandFromCommand:(WEB_COMMANDS_TYPE)type successHandler:(WTCommandSuccessBlock)successHandler errorHandler:(WTCommandErrorBlock)errorHandler{
WebCommands* ret=[[WebCommands alloc] init];
if (!ret) return nil;
switch (type) {
case WEB_COMMAND_TEST_GET:{
return [ret constructTestGetDataCommandSuccessHandler:successHandler errorHandler:errorHandler];
break;
}
case WEB_COMMAND_TEST_POST:{
return [ret constructTestPostDataCommandSuccessHandler:successHandler errorHandler:errorHandler];
break;
}
default:
return nil;
break;
}
}
因为我们在业务逻辑层和网络请求层多加了一层,我们在这层中设置网络请求调用的接口和路由,并且可以将返回结果放进指定回调,不必再由下游的业务逻辑层来做判断。
-(WTCommand*)constructTestGetDataCommandSuccessHandler:(WTCommandSuccessBlock)successHandler errorHandler:(WTCommandErrorBlock)errorHandler{
__block WTCommand* command=[[WTCommand alloc]initWithConsumeHandler:^(id input) {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"http://www.example.com" parameters:input progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(@"JSON: %@", responseObject);
command.successHandler(responseObject);
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(@"Error: %@", error);
command.errorHandler(error);
}];
} successHandler:successHandler errorHandler:errorHandler];
return command;
}
command的使用:
这样我们在业务逻辑层使用command的方式就变成了这样
// 通过工厂类创建command
WTCommand* command=[WebCommands constructCommandFromCommand:WEB_COMMAND_TEST_GET successHandler:^(id result) {
NSLog(@"result in WebCommand callback:: %@",result);
} errorHandler:^(id error) {
NSLog(@"error in WebCommand callback:: %@",error);
}];
// 设置输入参数并执行
[command excute:nil];
状态监控和final<a id="orgheadline5"></a>
再看一下我们的代码,command内部有状态的改变,但却没有一个统一的方法能设置状态改变后的回调,这就很不合适。
WTCommand
-(instancetype)initChangeStatusCallback:(WTCommandChangeStatusCallback)changeStatusCallback{
if (changeStatusCallback){
_changeStatusCallback=changeStatusCallback;
}
return self;
}
在每次改变状态后都调用一次changeStatusCallback
即可
因为业务逻辑层只能执行成功回调或者失败回调中的一种,如果我们需要在无论失败成功时都执行一段代码的话,就必须在成功回调和失败回调里插入final回调。做法同上,通过函数设置final回调,在生产command对象时将本来的成功失败回调替换,插入final回调。
-(instancetype)initChangeStatusCallback:(WTCommandChangeStatusCallback)changeStatusCallback{
if (changeStatusCallback){
_changeStatusCallback=changeStatusCallback;
}
return self;
}
-(instancetype)initWithConsumeHandler:(WTCommandConsumeBlock)consumeHandler successHandler:(WTCommandSuccessBlock)successHandler errorHandler:(WTCommandErrorBlock)errorHandler cancelHandler:(WTCommandCancelBlock)cancelHandler{
// other stuff
_successHandler=^(id result){
__strong typeof(self)strongself=weakself;
strongself.status=COMMAND_SUCCESS;
successHandler(result);
strongself.changeStatusCallback(COMMAND_SUCCESS);
strongself.finalHandler(result);
};
_errorHandler=^(id error){
__strong typeof(self)strongself=weakself;
strongself.status=COMMAND_ERROR;
errorHandler(error);
strongself.changeStatusCallback(COMMAND_ERROR);
strongself.finalHandler(error);
};
// other stuff
}
这里两个初始化函数的返回都是instancetype
,主要是我比较喜欢其它语言比如javascript的一路点下去的写法。
所以整个的用法是这样:
[[[[WebCommands constructCommandFromCommand:WEB_COMMAND_TEST_POST successHandler:^(id result) {
NSLog(@"result in WebCommand callback:: %@",result);
} errorHandler:^(id error) {
NSLog(@"error in WebCommand callback:: %@",error);
}] finalHandler:^(id output) {
NSLog(@"final in WebCommand callback:: %@",output);
}] initChangeStatusCallback:^(CommandStatus status) {
switch (status) {
case COMMAND_SUCCESS:
NSLog(@"successStatus");
break;
case COMMAND_ERROR:
NSLog(@"errorStatus");
break;
case COMMAND_PENDING:
NSLog(@"pendingStatus");
break;
default:
break;
}
}] excute:@{@"mobile":@"12345678",@"passwd":@"123456"}];
设计模式<a id="orgheadline7"></a>
最后来谈下设计模式,这里大概用上了command和工厂模式。事实上我并不是特别熟设计模式,但写代码时只要经常想着怎么把代码写的扩展性好、耦合度低,代码写的合理、优雅,模式自然而然会出现,当然经常借鉴和学习也是很重要的。