命令模式是行为型的设计模式,其核心思想很简单:将一个请求封装成一个对象,而且这个对象包含请求的全部信息(turns a request into a stand-alone object that contains all information about the request) 怎么理解呢?command 命令,这个单词的英文解释是 an authoritative direction or instruction to do something,而请求 request 能够简单理解成方法调用 to do something,所以,命令模式的核心就是将动词 to do something 转成了名词 command,封装命令类。git
看一下命令模式的类图:github
总之,能够将命令模式当作一个客人在餐厅点餐的过程:设计模式
直接调用方法不就好了?为何要将 request 封装成 command 呢?由于直接调用 request 时须要知道 request 全部细节,当 request 较多时也难以管理,而抽象成 command 就能够:网络
An NSInvocation object contains all the elements of an Objective-C message: a target, a selector, arguments, and the return value. Each of these elements can be set directly, and the return value is set automatically when the NSInvocation object is dispatched.异步
NSInvocation 是 iOS 中的系统类,基于命令模式,将 Objective-C 消息的全部信息封装到此类中,下面是使用 NSInvocation 的例子:函数
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:¶meters atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
NSInteger result = 0;
[invocation getReturnValue:&result];
复制代码
经过使用 NSInvocation,能够将方法的调用者与执行者隔离开,进行解耦(例如 CTMediator 和 JSPatch 都使用 NSInvocation 调用方法)。除了解耦,因为消息的信息都被封装到 NSInvocation 中,所以能够进行消息分发,例如能够修改 target 从而将消息转发给其余 target 或者修改 selector 从而将消息转发给其余 implemention。ui
我司的 YTKNetwork 网络库使用的也是命令模式:spa
将 API 请求封装成 YTKBaseRequest 的命令以后,Client 并不关心真正执行的是谁,目前是 AFNetworking,若是进行更换或者大版本升级,对 Client 没有影响。另外,能够对 Request 的 URL 进行 URL Filter,统一修改 URL 的某些值(例如 Common Arguments 或 Device),也能够对多个 Request 进行管理(不管是 ViewController 仍是 YTKNetworkAgent)。设计
应用内报 Bug 支持当前屏幕截图后进行绘制,而且绘制能够 Undo 和 Redo,那就很是适合命令模式:将每次绘制的动做抽象成 Action,Action 中包含了这次绘制的 Path 和 Color(实际上是 CAShapeLayer),用两个队列分别存储 Undo 和 Redo 的 Action:code
PM 曾提出需求,要求在启动时的弹窗可以按照顺序弹出,当一个弹窗关闭后再弹出一个,而不是一块儿弹出。
[AlertUtils showAlertWithTitle:title message:message buttonCallback:^{
// Do Something
}];
复制代码
上面的代码就是通常弹窗的使用方式,分析一下这个需求,问题核心是弹窗的结束是基于 UI 操做,是种异步操做,如何将异步的操做可以按照序列执行呢?注意,“方法 + 序列”是否是听起来很熟悉?因此这个需求就能够用命令模式来处理,将弹窗封装成命令后在串行队列中管理就好了。
具体作法是从 NSOperation 继承一个 AlertOperation,在 runInMain 函数中执行的 AlertUtils 的 showAlert 操做,并在其 buttonCallback 中调用 NSOperation 的 finishOperation。而全部的 AlertOperation 都是在 Serial Operation Queue 中,当前一个 Operation 没有变成 finished 时,后一个 Operation 是不会执行的,所以实现了 Alert 的按顺序弹出。
最先的 WebViewController 在处理 JS 回调的方法是用一堆 if/else if/else 语句:
- (void)jsCallback:(NSString *)name arguments:(NSDictionary *)arguments {
if ([name isEqualToString:@“command1”]) {
[self handleCommand1:name arguments:arguments];
} else if ([name isEqualToString:@“command2”]) {
[self handleCommand2:name arguments:arguments];
} else if ([name isEqualToString:@“command3”]) {
[self handleCommand3:name arguments:arguments];
} else if ([name isEqualToString:@“command4”]) {
[self handleCommand4:name arguments:arguments];
}
}
复制代码
这样写的问题是致使 WebViewController 愈来愈庞大,一堆业务逻辑耦合到 WebViewController 中(例如登陆通知,语音跟读的回调等),维护性变差。另外,若是想配置 WebViewController 只支持某些或者不支持某些 JS 特定的回调的话,甚至根据页面 URL 进行动态调整,也不是很干净。因而趁着 UIWebView 升级 WKWebView,作了一次重构:基于命令模式,将 JS 回调的处理抽离到一个个 Handler 中,JS 回调的名称和参数也在 Handler 中维护,WebViewController 中再也不含有任何与 WebView 无关的业务逻辑,当 WebView 触发了 JS 回调后,调用 Command Manager 这个 Invoker 去调用 Command。
- (void)registerCommands {
[self.commandManager registerCommand:[Command1Handler new]];
[self.commandManager registerCommand:[Command2Handler new]];
[self.commandManager registerCommand:[Command3Handler new]];
[self.commandManager registerCommand:[Command4Handler new]];
}
- (void)jsCallback:(NSString *)name arguments:(NSDictionary *)arguments {
JSCommand *command = [JSCommand commandWithName:name arguments:arguments];
[self.commandManager handleCommand:command];
}
复制代码
命令模式的核心在于将一个 to do something 的动做抽象成 command 对象,而不要太纠结于 Invoker 是谁,Client 在哪?一旦 command 接口抽象完,Client、Invoker、Receiver 天然而然的能找到。使用命令模式的优势是:
参考:
Article by Joe Shang