定义
命令模式(Command Pattern):命令(或请求)被封装成对象。客户端将命令(或请求)对象先传递给调用对象。调用对象再把该命令(或请求)对象传给合适的,可处理该命令(或请求)的对象来做处理。
由定义可以看出,在命令模式中,命令被封装成了对象,而发送命令的客户端与处理命令的接收者中间被调用对象隔开了,这种设计的原因或者适用的场景是什么样的呢?
在有些场景下,任务的处理可能不是需要立即执行的:可能需要记录(日至),撤销或重试(网络请求)。那么在这些场景下,如果任务的请求者和执行者是紧耦合状态下的话就可能会将很多其他执行策略的代码和立即执行的代码混合到一起。
这些其他执行策略,我们暂时称之为控制和管理策略,而如果我们如果想控制和管理请求,就需要:
因此命令模式就是为此场景量身打造的,它通过:
现在我们清楚了命令模式的适用场景,下面看一下命令模式的成员和类图。
不包括请求的发起者(客户端),命令模式共有四个成员:
下面通过类图来看一下命令模式各个成员之间的关系:
命令模式类图
模拟一个使用遥控器开灯和关灯的例子。
在这个例子中,使用遥控器的人就是客户端,TA发起开启或关闭灯的命令给遥控器(调用者)。然后调用者将命令传递给接收者(灯)。
在这里,人是不直接接触灯的,开启和关闭的命令是通过遥控器来做的转发,最后传达给灯来执行。
下面我们看一下如何用代码来模拟该场景。
首先我们创建接收者,灯类:
//================== Light.h ==================
@interface Light : NSObject
- (void)lightOn;
- (void)lightOff;
@end
//================== Light.m ==================
@implementation Light
- (void)lightOn{
NSLog(@"Light on");
}
- (void)lightOff{
NSLog(@"Light off");
}
@end
灯类声明并实现了两个接口:开灯接口和关灯接口,来让外部执行开灯和关灯的操作。
接着我们创建抽象命令类和具体命令类:
抽象命令类:
//================== Command.h ==================
@interface Command : NSObject
- (void)excute;
@end
//================== Command.m ==================
@implementation Command
@end
抽象命令类声明了一个执行命令的接口excute
,这个接口由它的子类,也就是具体命令类来实现。
因为这里面只有开灯和关灯两种命令,所以我们创建两个具体命令类来继承上面的抽象命令类:
开灯命令CommandLightOn
:
//================== CommandLightOn.h ==================
@interface CommandLightOn : Command
- (instancetype)initWithLight:(Light *)light;
@end
//================== CommandLightOn.m ==================
@implementation CommandLightOn
{
Light *_light;
}
- (instancetype)initWithLight:(Light *)light{
self = [super init];
if (self) {
_light = light;
}
return self;
}
- (void)excute{
[_light lightOn];
}
关灯命令CommandLightOff
:
//================== CommandLightOff.h ==================
@interface CommandLightOff : Command
- (instancetype)initWithLight:(Light *)light;
@end
//================== CommandLightOff.m ==================
@implementation CommandLightOff
{
Light *_light;
}
- (instancetype)initWithLight:(Light *)light{
self = [super init];
if (self) {
_light = light;
}
return self;
}
- (void)excute{
[_light lightOff];
}
我们可以看到这两个具体命令类分别以自己的方式实现了它们的父类声明的
excute
接口。
最后我们创建链接客户端和接收者的调用者类,也就是遥控器类RemoteControl
:
//================== RemoteControl.h ==================
@interface RemoteControl : NSObject
- (void)setCommand:(Command *)command;
- (void)pressButton;
@end
//================== RemoteControl.m ==================
@implementation RemoteControl
{
Command *_command;
}
- (void)setCommand:(Command *)command{
_command = command;
}
- (void)pressButton{
[_command excute];
}
@end
遥控器类使用set
方法注入了具体命令类,并向外提供了pressButton
这个方法来内部调用已传入的具体命令类的excute
方法。
最后我们看一下客户端是如何操作这些类的:
//================== client ==================
//init Light and Command instance
//inject light instance into command instance
Light *light = [[Light alloc] init];
CommandLightOn *co = [[CommandLightOn alloc] initWithLight:light];
//set command on instance into remote control instance
RemoteControl *rm = [[RemoteControl alloc] init];
[rm setCommand:co];
//excute command(light on command)
[rm pressButton];
//inject light instance into command off instance
CommandLightOff *cf = [[CommandLightOff alloc] initWithLight:light];
//change to off command
[rm setCommand:cf];
//excute command(light close command)
[rm pressButton];
看一下日至输出:
[11851:1190777] Light on
[11851:1190777] Light off
从上面的代码可以看到,我们首先准备好具体命令类的实例,然后将其传递给遥控器类,最后触发遥控器的pressButton
方法来间接触发light
对象的相应操作。
下面看一下上面代码对应的类图。
命令模式代码示例类图
java.lang.Runnable
是使用命令模式的经典场景,Runnable接口可以作为抽象的命令,而实现了Runnable的线程即是具体的命令。