定义
它属于行为型模式,将一个请求(开灯)封装成一个(LightOnCommand)对象,从而让你使用不同的请求把客户端参数化,请求(开灯)以命令的形式包裹在(LightOnCommand)对象中,并传递给调用对象(RemoteControl)。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。降低耦合。
使用场景
1.应用程序支持撤销和恢复
2.记录请求日志,当系统故障时可以重新被执行
3.想用对象参数化一个动作以执行操作并且用不同命令来替换回调函数。
优势
命令模式让命令的发起者和命令的接受者完全解耦,在他们之间通过命令对象来实现连通。这样就有如下好处:
- 把请求封装起来,可以动态的进行队列化、持久化等操作,满足各种业务需求。
- 可以把多个简单的命令组合成一个宏命令,从而简化操作,完成复杂的功能
- 扩展性良好。由于命令的发起者和接受者完全解耦,那么这个时候添加和修改任何命令,只需要设置好对应的命令和命令对象即可,无需对原有系统进行修改
UML结构图及说明

真实场景分析
生活中有种遥控器叫万能遥控器,就是那种能遥控各种品牌的空调或者电视的遥控器,我们只要在遥控器上设定具体的电器品牌,就可以遥控了。我们来简单实现这个功能,假设,电器只有开和关两个功能。
需求:用户可以随意设置电器,然后按下打开或者关闭按钮,就可以执行该电器的打开和关闭功能。
分析:我们用编程的思维抽象思考下,打开和关闭电器是一个请求命令,而具体的电器是命令的接收者,要想实现一个请求命令可以被任意命令接收者执行,那么就必须对请求命令和具体接收者进行解耦,让请求命令不必关心具体的命令接收者和命令执行的具体细节,只管发出命令,然后就可以被具体的命令接收者接收并执行。
那么如何实现二者的解耦呢?要让两者互相不知道,那么就必须引入一种中间量,这就是命令对象,命令对象会暴露一个接口,具体的命令对象来实现这个接口,同时每个命令对象都会和具体的命令接收者关联,调用命令接收者的功能。
具体代码实现
1.定义命令对象,所有具体的命令对象都要实现这些方法
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol CommandInterface <NSObject>
//定义命令对象,所有具体的命令对象都要实现这些方法
- (void)execute;
- (void)undo;
@end
NS_ASSUME_NONNULL_END
2.定义具体命令对象LightOnCommand(每个遥控器按钮关联的操作)
#import "LightOnCommand.h"
@interface LightOnCommand ()
@property (nonatomic, strong) Light *light;
@end
@implementation LightOnCommand
//初始化的时候需要传入命令的接受者
- (instancetype)initWithLight:(Light *)light {
self = [super init];
if (self) {
self.light = light;
}
return self;
}
//具体对象会实现execute方法,调用命令接受者的实现功能
- (void)execute {
[self.light lightOn];
}
- (void)undo {
[self.light lightOff];
}
@end
具体对象LightOffCommand
#import "LightOffCommand.h"
@interface LightOffCommand ()
@property (nonatomic, strong) Light *light;
@end
@implementation LightOffCommand
- (instancetype)initWithLight:(Light *)light {
self = [super init];
if (self) {
self.light = light;
}
return self;
}
- (void)execute {
[self.light lightOff];
}
- (void)undo {
[self.light lightOn];
}
@end
3.定义命令对象具体接收者(各种电器)
Light.h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
//命令的接受者
@interface Light : NSObject
- (void)lightOn;
- (void)lightOff;
@end
NS_ASSUME_NONNULL_END
Light.m文件
#import "Light.h"
@implementation Light
- (void)lightOn {
NSLog(@"灯被打开");
}
- (void)lightOff {
NSLog(@"灯被关闭");
}
@end
4.设置命令调用者(遥控器)
我们现在需要定义遥控器每排按钮的命令,我们把打开和关闭的命令对象分别存入到两个不同的数组,然后根据按钮的排数去数组中取出响应的命令执行。
RemoteControl.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface RemoteControl : NSObject
//处理on命令,供client调用
- (void)onWithIndex:(NSInteger)index;
//处理off命令,供client调用
- (void)offWithIndex:(NSInteger)index;
//撤销所有的操作,供client调用
- (void)undoAllOperations;
@end
NS_ASSUME_NONNULL_END
RemoteControl.m
#import "RemoteControl.h"
#import "CommandInterface.h"
#import "Light.h"
#import "LightOnCommand.h"
#import "LightOffCommand.h"
#import "CDPlayer.h"
#import "CDPlayerOnCommand.h"
#import "CDPlayerOffCommand.h"
#import "MacroCommand.h"
@interface RemoteControl ()
@property(nonatomic,strong)NSMutableArray<id<CommandInterface>> *onCommandArr;
@property(nonatomic,strong)NSMutableArray<id<CommandInterface>> *offCommandArr;
@property(nonatomic,strong)NSMutableArray<id<CommandInterface>> *completedCommandArr;
@end
@implementation RemoteControl
- (instancetype)init {
self = [super init];
if (self) {
self.onCommandArr = [NSMutableArray new];
self.offCommandArr = [NSMutableArray new];
self.completedCommandArr = [NSMutableArray new];
[self configCommands];
}
return self;
}
//命令调用者(RemoteControl)和命令接受者(Light和CDPlayer)完全解耦,只需要通过命令对象这个中间变量来联系。这样不管命令接受者如何变化,只需要改变命令调用者的每个命令关联的命令对象即可,而无需更改命令调用者的任何代码,而客户端是面向的命令调用者编程,那么客户端代码也不需要修改,只需要扩展即可。
//配置具体的命令对象
- (void)configCommands {
Light *light = [[Light alloc] init];
LightOnCommand *lightOn = [[LightOnCommand alloc] initWithLight:light];
LightOffCommand *lightOff = [[LightOffCommand alloc] initWithLight:light];
RemoteControl *rc = [[RemoteControl alloc] init];
[rc setCommandWithIndex:0 onCommand:lightOn offCommand:lightOff];
CDPlayer *player = [[CDPlayer alloc] init];
CDPlayerOnCommand *playerOn = [[CDPlayerOnCommand alloc] initWithCDPlayer:player];
CDPlayerOffCommand *playerOff = [[CDPlayerOffCommand alloc] initWithCDPlayer:player];
[rc setCommandWithIndex:1 onCommand:playerOn offCommand:playerOff];
}
//绑定具体的命令对象
- (void)setCommandWithIndex:(NSInteger)index onCommand:(id<CommandInterface>)onCommand offCommand:(id<CommandInterface>)offCommand {
self.onCommandArr[index] = onCommand;
self.offCommandArr[index] = offCommand;
}
//调用命令对象的execute方法
- (void)onWithIndex:(NSInteger)index {
[self.onCommandArr[index] execute];
[self.completedCommandArr addObject:self.onCommandArr[index]];
}
- (void)offWithIndex:(NSInteger)index {
[self.offCommandArr[index] execute];
[self.completedCommandArr addObject:self.offCommandArr[index]];
}
- (void)undoAllOperations {
for (id <CommandInterface>command in self.completedCommandArr) {
[command undo];
}
}
@end
5.客户端调用
现在我们已经完成了遥控器的实现,分别是电灯和CD的打开关闭操作。下面我们来看看调用
#import "Client.h"
#import "RemoteControl.h"
@implementation Client
- (void)test {
RemoteControl *rc = [[RemoteControl alloc] init];
[rc onWithIndex:0];
[rc offWithIndex:0];
[rc onWithIndex:1];
[rc offWithIndex:1];
}
@end
小结
通过上面的例子我们可以看到命令调用者和命令接受者之间完全解耦了,二者都不需要知道对方,只需要通过命令对象这个中间量来联系即可,这样不管命令接受者如何变化,只需要改变命令调用者的每个命令关联的命令对象即可,而无需更改命令调用者的任何代码,而客户端是面向的命令调用者编程,那么客户端代码也不需要修改,只需要扩展即可。
宏命令
生活中有这个场景:我们下班回家,一进家门,打开电灯,然后打开CD播放器。但是使用上面的代码我们需要一个个的电器去打开,太麻烦,能不能实现按一个按钮就打开电灯和CD播放器呢?这就需要用到宏命令,所谓宏命令就是把许多命令对象集中起来一起执行。
另外我们我们还可以实现命令撤销功能,这里的命令撤销功能比较简单,只需要执行相反操作就可以了,比如执行的动作是开灯,那么撤销操作就是关灯。
1.实现宏命令(将许多命令对象集中起来一起执行)
MacroCommand.h
#import <Foundation/Foundation.h>
#import "CommandInterface.h"
NS_ASSUME_NONNULL_BEGIN
//宏命令:将许多命令对象集中起来一起执行
@interface MacroCommand : NSObject <CommandInterface>
- (instancetype)initWithCommands:(NSArray<id <CommandInterface>> *)commands;
@end
NS_ASSUME_NONNULL_END
MacroCommand.m
#import "MacroCommand.h"
@interface MacroCommand ()
@property(strong,nonatomic)NSArray<id<CommandInterface>> *commandsArr;
@end
@implementation MacroCommand
- (instancetype)initWithCommands:(NSArray<id <CommandInterface>> *)commands {
self = [super init];
if (self) {
self.commandsArr = commands;
}
return self;
}
- (void)execute {
for (id <CommandInterface> command in self.commandsArr) {
[command execute];
}
}
- (void)undo {
for (id <CommandInterface> command in self.commandsArr) {
[command undo];
}
}
@end
RemoteControl在配置具体命令对象的时候也需要改一下
- (void)configCommands {
Light *light = [[Light alloc] init];
LightOnCommand *lightOn = [[LightOnCommand alloc] initWithLight:light];
LightOffCommand *lightOff = [[LightOffCommand alloc] initWithLight:light];
CDPlayer *player = [[CDPlayer alloc] init];
CDPlayerOnCommand *playerOn = [[CDPlayerOnCommand alloc] initWithCDPlayer:player];
CDPlayerOffCommand *playerOff = [[CDPlayerOffCommand alloc] initWithCDPlayer:player];
MacroCommand *onMacro = [[MacroCommand alloc] initWithCommands:@[lightOn, playerOn]];
MacroCommand *offMacro = [[MacroCommand alloc] initWithCommands:@[lightOff, playerOff]];
RemoteControl *rc = [[RemoteControl alloc] init];
[rc setCommandWithIndex:0 onCommand:onMacro offCommand:offMacro];
}
2.撤销命令
给命令对象接口添加undo功能,然后具体命令对象实现undo,代码里有实现,这里就不讲了。
以上解决问题的思路就是使用的命令模式。
网友评论