前言
在战场上,将军把封在信封里的定时指令交给不下,然后他们到时候打开信封并执行其中的命令。同样的指令既可以是一次性的,也可以是能被不同人在指定时间再次执行的。因为指令封在信封里,所以为了各种目的在不同地区之间传递起来会比其它方式更为容易。
在面向对象设计中,我们借用了类似的思想,把指令封装在各种命令对象中。命令对象可以被传递并且在指定时刻被不同的客户端复用。从这一概念设计而来的设计模式叫做命令模式。
什么是命令模式
命令设计模式将请求封装为对象,从而可以使用不同的请求对客户进行参数化,对请求队列或记录请求日志,以及支持可撤消的操作。命令对象封装了如何对目标执行指令的信息。因此客户端或调用者不必了解目标的任何细节,却仍可以对它执行任何已有的操作。命令对象将一个或多个动作绑定到特定的接收器,命令模式消除了作为对象的动作和执行它的接收器之间的绑定。
-
Client
创建ConcreteCommand
对象并设定其Receiver
-
Invoker
要求通用命令实施请求 -
Command
为Invoker
所知的协议 -
ConcreteCommand
起到Receiver
和action
之间的中间人的作用 -
Receiver
可以是随着由ConcreteCommand
对象实施的相应请求,而执行实际操作的任何对象。
什么时候使用命令模式
- 想让应用程序支持撤销与恢复
- 想用对象参数化一个动作以执行操作,并用不同命令对象来代替回调函数
- 想要在不同时刻对请求进行指定、排列和执行。
- 想要记录修改日志,这样在系统故障时,这些修改可以在后来重做一遍。
- 想让系统支持事务,事务封装了对数据的一系列修改。事务可以建模为命令对象。
命令模式的优缺点
命令模式的优点
- 降低了系统耦合度。
- 新的命令可以很容易添加到系统中去。
命令模式的缺点
使用命令模式可能会导致某些系统有过多的具体命令类。
Cocoa中的命令模式
NSInvocation
NSInvocation
类的实例封装了一个Objective-C 消息。调用对象包含目标对象(target)、方法选择器(selector)和方法参数(parameters)。可以借助于 NSInvocation
动态更改调用对象发送的消息的目标及其参数,还可以从对象中获取返回值。使用同一个 NSInvocation 实例可以重复调用接收器的同一个方法,或者通过不同的目标和方法签名进行复用。
NSInvocation
对象的创建需要 NSMethodSignature
对象。 NSMethodSignature
对象封装了方法的参数和返回值相关的类型信息。NSMethodSignature
对象是通过方法选择器创建的。NSInvocation
的实现还利用了 Objective-C 运行时的功能。
Target - Action
Target - Action 机制使控制对象(即按钮、滑块或文本字段等对象)能够将消息发送到另一个对象,该对象可以解释消息并将其作为特定于应用程序的指令进行处理。接收对象(target),通常是一个自定义的控制器对象。消息——命名为动作消息——由选择器决定,一个方法的唯一运行时标识符。
NSUndoManager
NSUndoManager
作为通用的撤销栈的管理类,其撤销栈以对象形式保持所有已调用的操作。通过调用从撤销栈压入恢复栈的操作对象。NSUndoManager
在两个栈之间移动操作对象,管理着整个命令历史记录。
命令模式的实现
首先创建作为命令的接口 <Order>
@protocol Order <NSObject>
- (void)execute;
@end
创建作为请求的 Stock
类
@interface Stock : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int quantity;
- (void)buy;
- (void)sell;
@end
@implementation Stock
- (void)buy{
NSLog(@"Buy");
}
- (void)sell{
NSLog(@"Sell");
}
@end
创建实体命令类 BuyStock
和 SellStock
,实现 <Order>
接口,将执行实际的命令处理
@interface BuyStock : NSObject <Order>
- (instancetype)initWithStock:(Stock *)abcStock;
@end
@implementation BuyStock
{
Stock *_aStock;
}
- (instancetype)initWithStock:(Stock *)aStock;
{
self = [super init];
if (self) {
_aStock = aStock;
}
return self;
}
- (void)execute{
[_aStock buy];
}
@end
@interface SellStock : NSObject <Order>
- (instancetype)initWithStock:(Stock *)abcStock;
@end
@implementation SellStock
{
Stock *_aStock;
}
- (instancetype)initWithStock:(Stock *)aStock;
{
self = [super init];
if (self) {
_aStock = aStock;
}
return self;
}
- (void)execute{
[_aStock sell];
}
@end
创建作为调用对象的类 Broker
,它接受订单并能下订单。Broker
对象使用命令模式,基于命令的类型确定哪个对象执行哪个命令。
@interface Broker : NSObject
- (void)takeOrder:(id <Order>)order;
- (void)placeOrders;
@end
@implementation Broker{
NSMutableArray <id<Order>>* _orderList;
}
- (instancetype)init
{
self = [super init];
if (self) {
_orderList = [NSMutableArray array];
}
return self;
}
- (void)takeOrder:(id <Order>)order{
[_orderList addObject:order];
}
- (void)placeOrders{
for (id <Order> order in _orderList) {
[order execute];
}
[_orderList removeAllObjects];
}
@end
最后使用 Broker
类来演示命令模式。
Stock *aStock = [[Stock alloc] init];
aStock.name = @"ABC";
aStock.quantity = 10;
BuyStock *buyStockOrder = [[BuyStock alloc] initWithStock:aStock];
SellStock *sellStockOrder = [[SellStock alloc] initWithStock:aStock];
Broker *broker = [[Broker alloc] init];
[broker takeOrder:buyStockOrder];
[broker takeOrder:sellStockOrder];
[broker placeOrders];
总结
在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。命令模式通过调用者调用接受者执行命令,顺序:调用者→命令→接受者。命令模式可以在单击试图控制器中的按钮时,执行一个命令对象,对另一个视图控制器进行某些操作,也可以实现撤销和恢复时使用。