命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令
场景
比如电脑开机,用户按钮开机按钮,等待就可以了,但是谁来处理的?怎么处理?经历了什么样的过程电脑才真正启动起来的?
上面所提的问题,我们平时关心吗?答案是肯定的,我们不关心,我们只需要按下开机按钮,等着电脑启动使用即可.那么上面的问题如果用编程来实现,怎么实现呢?
首先把上面的问题抽象描述一下:客户端只是想要发出命令或者请求,不关心请求的真正接收者是谁,也不关心具体如何实现,而且同一个请求的动作可以有不同的请求内容,当然具体的处理功能也不一样,请问该怎么实现?
要想实现一个请求命令可以被任意命令接受者执行,那么必须对二者进行解耦,让请求命令不必关心具体的命令接受者和命令执行的具体细节,只管发出命令,然后就可以被具体的命令接受者接受并执行。
那么如何实现二者的解耦呢?要让两者互相不知道,那么就必须引入一种中间量,这就是命令对象.它会把按钮和一个具体的命令对象关联起来,命令对象会暴露一个接口给按钮使用,同时每个命令对象都会和具体的命令接受者关联,调用命令接受者的功能。此时按下按钮,就会调用关联的命令对象的公开接口,然后命令对象内部在调用具体的命令接受者(主板)的公开方法,执行功能。
说了半天大家可能还是不太明白什么是命令模式?下面简单来说:
在很多时候,软件系统中,“行为请求者"和"行为实现者"通常呈现一种"紧耦合”.比如按钮的点击事件,虽然你有时候会把事件代码单独摘出来让一个实现类去实现,但实际上实现类和按钮之间是一种紧耦合.但某些场合,比如要对行为进行"记录,撤销/重做"等处理,这种无法抵御变化的紧耦合是不合适的.在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间
的松耦合。这就是命令模式(Command Pattern)。
模式结构和说明
Command:定义命令的接口,声明执行的方法
ConcreteCommand:命令接口实现对象,是"虚"的实现,通常会持有接收者,并调用接受者的功能来完成命令要执行的操作
Receiver:接收者,真正执行命令的对象.任何类都有可能成为一个接收者,只要它能够实现命令要求实现的相应功能
Invoker:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
Client:创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。
示例代码
//示例:改变view的明亮度
不使用模式的代码
先看看没有使用命令模式的代码
- UI绘制(添加两个按钮,一个增加亮度,一个减少亮度),代码忽略
- 创建事件处理者
@implementation Receiver
- (void)bindView:(UIView *)view
{
_colorV = view;
UIColor *color = view.backgroundColor;
[color getHue:&_hue saturation:&_saturation brightness:&_brightness alpha:&_alpha];
}
- (void)makeViewLighter:(CGFloat)quantity
{
_brightness += quantity;
_brightness = MIN(1, _brightness);
self.colorV.backgroundColor = [UIColor colorWithHue:_hue saturation:_saturation brightness:_brightness alpha:_alpha];
}
- (void)makeViewDarker:(CGFloat)quantity
{
_brightness -= quantity;
_brightness = MAX(0, _brightness);
self.colorV.backgroundColor = [UIColor colorWithHue:_hue
saturation:_saturation
brightness:_brightness
alpha:_alpha];
}
@end
- 创建客户端接收器
// 创建客户端接收器
self.reciver = [[Receiver alloc] init];
[self.reciver bindView:self.view];
- 按钮时间处理
- (void)buttonsEvent:(UIButton *)button {
if (button.tag == kAddButtonTag) {
[self.reciver makeViewLighter:0.1];
} else if (button.tag == kDelButtonTag) {
[self.reciver makeViewDarker:0.1];
}
}
上面的代码没有问题,这是肯定的.完全能实现我们的需求.但仔细分析一下就会发现一个问题,上面的行为请求者是self.reciver,而行为实现者呢:self.view,两者之间是紧耦合的,对于要对命令的记录,撤销等操作就不好做了.比如第一次亮度增加0.2,现在想撤销之前的命令,有人会想那不是很简单,直接变暗0.2不就行了,可以是可以,但你有没有想过,之前亮度增加的0.2,你得记录啊,不记录,你知道它亮度变了多少吗?还要记录上次是变亮还是变暗你才能决定这次的操作.再复杂一点,第一次变亮0.2,第二次变暗0.1,现在我想回到原始状态你怎么办?看到这你是不是蒙圈了?别急,咱们下面来用命令模式解决上面的问题
使用命令模式的代码
-
UI绘制(添加三个按钮,一个增加亮度,一个减少亮度,一个撤销按钮),代码忽略
-
事件处理者(同上)
-
定义命令的接口,声明执行的方法
@protocol CommandInterface <NSObject>
- (void)execute;
- (void)rollBack;
@end
- 定义命令接口实现对象,持有接收者,并调用接受者的功能来完成命令要执行的操作
//变亮命令(持有receiver,用receiver实现真正的变亮)
@interface LighterCommand()<CommandInterface>
/**/
@property (nonatomic,strong) Receiver *receiver;
/**/
@property (nonatomic,assign) CGFloat parameter;
@end
@implementation LighterCommand
- (id)initWithReceiver:(Receiver*)receiver parameter:(CGFloat)parameter {
self = [super init];
if (self) {
_receiver = receiver;
_parameter = parameter;
}
return self;
}
- (void)execute
{
[self.receiver makeViewLighter:self.parameter];
}
- (void)rollBack
{
[self.receiver makeViewDarker:self.parameter];
}
@end
//变暗命令(持有receiver,用receiver实现真正的变暗)
@interface DarkerCommand()<CommandInterface>
/**/
@property (nonatomic,strong) Receiver *receiver;
/**/
@property (nonatomic,assign) CGFloat parameter;
@end
@implementation DarkerCommand
- (id)initWithReceiver:(Receiver*)receiver parameter:(CGFloat)parameter {
self = [super init];
if (self) {
_receiver = receiver;
_parameter = parameter;
}
return self;
}
//命令的执行(需要命令的真正执行者,也就是具有命令行为和作用于服务对象的那个Receiver对象,因为Receiver中有直接作用于服务对象view的行为)
- (void)execute
{
[self.receiver makeViewDarker:self.parameter];
}
- (void)rollBack
{
[self.receiver makeViewLighter:self.parameter];
}
@end
- 创建命令管理类,会持有命令对象(单例类)
@interface Invoker()
/*存储命令*/
@property (nonatomic,strong) NSMutableArray *queue;
@end
@implementation Invoker
+ (instancetype)shareInstance {
static Invoker *shareInstanceValue = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
shareInstanceValue = [[Invoker alloc] init];
shareInstanceValue.queue = [NSMutableArray array];
});
return shareInstanceValue;
}
//添加命令
- (void)addAndExcute:(id<CommandInterface>)command
{
NSParameterAssert(command);
[self.queue addObject:command];
[command execute];
}
//撤销命令
- (void)rollBackAndExcute
{
if (self.queue.count == 0) return;
[self.queue.lastObject rollBack];
[self.queue removeLastObject];
}
@end
- 客户端使用
- (void)buttonsEvent:(UIButton *)button {
if (button.tag == kAddButtonTag) {
//不使用命令模式
// [self.reciver makeViewLighter:0.1];
//使用命令模式
LighterCommand *command = [[LighterCommand alloc] initWithReceiver:self.reciver parameter:0.1];
[[Invoker shareInstance] addAndExcute:command];
} else if (button.tag == kDelButtonTag) {
//不使用命令模式
// [self.reciver makeViewDarker:0.1];
//使用命令模式
// 生成命令
DarkerCommand *command = [[DarkerCommand alloc] initWithReceiver:self.reciver parameter:0.2];
// 执行命令
[[Invoker shareInstance] addAndExcute:command];
}
}
//撤销
- (void)backAction
{
[[Invoker shareInstance] rollBackAndExcute];
}
模式讲解
1. 命令模式的关键
命令模式的关键之处就是把请求封装成为对象,也就是命令对象,并定义了统一的执行操作的接口,这个命令对象可以被存储,转发,记录,处理,撤销等,整个命令模式都是围绕这个对象在进行
2. 命令模式的组装和调用
在命令模式中经常会有一个命令的组装者,用它来维护命令的"虚"实现和真实实现之间的关系.
3. 发起请求的对象和真正实现的对象是解耦的
请求究竟由谁处理,如何处理,发起请求的对象是不知道的,也就是发起请求的对象和真正实现的对象是解耦的。发起请求的对象只管发出命令,其它的就不管了