一 基本概念
- 定義:它屬于行為型模式,將一個請求封裝成一個對象,從而讓你使用不同的請求把客戶端參數(shù)化,請求以命令的形式包裹在對象中,并傳給調用對象。調用對象尋找可以處理該命令的合適的對象,并把該命令傳給相應的對象,該對象執(zhí)行命令。降低耦合度。
- 使用場景:應用程序支持撤銷和恢復;記錄請求日志,當系統(tǒng)故障時可以重新被執(zhí)行;想用對象參數(shù)化一個動作以執(zhí)行操作并且用不同命令來替換回調函數(shù)。
-
角色劃分:
接收者-> Reciever
命令接口-> Command(CommandProtocol協(xié)議)
具體命令-> ConcrateCommand
請求者-> Invoker:持有命令對象的引用 然后執(zhí)行對象的命令
Client類:最終的客戶端調用類。
二 基礎案例
- CommandProtocol
//命令接口-協(xié)議
@protocol Meryin_CommandProtocol<NSObject>
//操作->具體實現(xiàn)方法
-(void)execute;
@end
- 接收者Reciever實現(xiàn)具體方法功能
//接收者
@interface Meryin_Reciever : NSObject
//具體方法
-(void)start;
@end
@implementation Meryin_Reciever
-(void)start{
NSLog(@"接收者實現(xiàn)具體方法的功能");
}
@end
- ConcrateCommand 遵守協(xié)議,持有接收者,實現(xiàn)協(xié)議具體方法,調用具體邏輯
//具體命令-> ConcrateCommand
//遵守協(xié)議,實現(xiàn)協(xié)議具體方法
@interface Meryin_ConcrateCommand : NSObject<meryin_CommandProtocol>
- (instancetype)initWithReciever:(Meryin_Reciever *)reciever;
@end
@interface Meryin_ConcrateCommand()
@property(nonatomic, strong) Meryin_Reciever* reciever;
@end
@implementation Meryin_ConcrateCommand
- (instancetype)initWithReciever:(Meryin_Reciever *)reciever
{
self = [super init];
if (self) {
self.reciever = reciever;
}
return self;
}
-(void)execute{
[self.reciever start];
}
@end
- Invoker 請求者,持有ConcrateCommand的引用,執(zhí)行ConcrateCommand遵守協(xié)議的方法;保存操作記錄支持回退撤銷。
@interface Meryin_Invoker()
@property (nonatomic,strong)NSMutableArray *commands;
@property (nonatomic,strong)Meryin_Reciever *reciever;
@property(nonatomic, strong)id <Meryin_CommandProtocol> command;
@end
@implementation Meryin_Invoker
- (instancetype)initWith:(Meryin_ConcrateCommand *)command reciever:(Meryin_Reciever *)reciver
{
self = [super init];
if (self) {
self.command = command;
self.reciever = reciver;
self.commands = [NSMutableArray array];
}
return self;
}
-(void)toStart{
[self.command execute];
[self.commands addObject:[[Meryin_ConcrateCommand alloc]initWithReciever:self.reciever] ];
}
//支持撤銷
//撤銷最后一個命令
- (void)undoLastOne{
if (self.commands.count >0) {
NSLog(@"撤銷--%lu",self.commands.count-1);
//撤銷
[[self.commands lastObject] execute];
//移除
[self.commands removeLastObject];
}
}
- Client 最后調用
//創(chuàng)建接收者
Meryin_Reciever *reciver = [[Meryin_Reciever alloc]init];
//創(chuàng)建命令(分離)->解藕和
Meryin_ConcrateCommand *command = [[Meryin_ConcrateCommand alloc]initWithReciever:reciver];
//創(chuàng)建請求者
Meryin_Invoker *invoker = [[Meryin_Invoker alloc]initWith:command reciever:reciver];
[invoker toStart];
[invoker toStart];
[invoker undoLastOne];
三 基礎命令模式的一步步優(yōu)化
1. 動態(tài)命令
以上創(chuàng)建的命令模式,當命令過多時,類就會太多,不好管理,如果使用動態(tài)調用,用Block回調,不需要新建各種命令類,用Block實現(xiàn)一個命令類里面可以動態(tài)創(chuàng)建多個命令。
- 創(chuàng)建動態(tài)命令類DynamicCommand,持有block,可以動態(tài)創(chuàng)建命令實現(xiàn)接收者的各種方法。
typedef void(^dynamicBlcok)(Meryin_Reciever *);
//解決方法:用block實現(xiàn)
@interface DynamicCommand : NSObject<meryin_CommandProtocol>
- (instancetype)initWith:(Meryin_Reciever *)reciver block:(dynamicBlcok)block;
//創(chuàng)建命令
+(id<meryin_CommandProtocol>)createCommand:(Meryin_Reciever *)reciver block:(dynamicBlcok)block;
@end
@interface DynamicCommand()
@property(nonatomic, strong) Meryin_Reciever* reciever;
@property(nonatomic, copy) dynamicBlcok block;
@end
@implementation DynamicCommand
- (instancetype)initWith:(Meryin_Reciever *)reciver block:(dynamicBlcok)block
{
self = [super init];
if (self) {
self.reciever = reciver;
self.block = block;
}
return self;
}
- (void)execute{
if (self.block) {
self.block(self.reciever);
}
}
//創(chuàng)建命令
+(id<meryin_CommandProtocol>)createCommand:(Meryin_Reciever *)reciver block:(dynamicBlcok)block{
return [[DynamicCommand alloc]initWith:reciver block:block];
}
- 動態(tài)命令的請求者,動態(tài)添加命令,實現(xiàn)接收者方法的回調
@interface DynamicCommandManager()
@property (nonatomic,strong)NSMutableArray *commands;
@property (nonatomic,strong)Meryin_Reciever *reciever;
@end
@implementation DynamicCommandManager
- (instancetype)initWith:(Meryin_Reciever *)reciver{
self = [super init];
if (self) {
self.reciever = reciver;
self.commands = [NSMutableArray array];
}
return self;
}
-(void)toStart{
[self addCommand:@"start"];
[ self.reciever start];
}
- (void)toEnd{
[self addCommand:@"end"];
[self.reciever end];
}
- (void)addCommand:(NSString *)methodName{
//動態(tài)添加命令
DynamicCommand *command =[DynamicCommand createCommand:self.reciever block:^(Meryin_Reciever *block) {
SEL method = NSSelectorFromString(methodName);
//執(zhí)行回調
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[block performSelector:method];
#pragma clang diagnostic pop
}];
[self.commands addObject:command];
}
//撤銷最后一個命令
- (void)undoLastOne{
if (self.commands.count >0) {
NSLog(@"撤銷--%lu",self.commands.count-1);
//撤銷
[[self.commands lastObject] execute];
//移除
[self.commands removeLastObject];
}
}
//撤銷所有
- (void)undoAll{
if (self.commands.count >0) {
NSLog(@"撤銷所有");
//撤銷
for (id <meryin_CommandProtocol> command in self.commands) {
[command execute];
}
//移除
[self.commands removeAllObjects];
}
}
- Client客戶端調用
//動態(tài)命令
Meryin_Reciever *reciver1 = [[Meryin_Reciever alloc]init];
DynamicCommandManager *manager1 = [[DynamicCommandManager alloc]initWith:reciver1];
[manager1 toEnd];
[manager1 toStart];
[manager1 toStart];
[manager1 toEnd];
[manager1 undoByIndex:2];
命令模式并不去實現(xiàn)具體業(yè)務邏輯,只用于記錄撤銷等。具體撤銷邏輯由Client決定,命令模式只是保存操作。
2. 復合命令
執(zhí)行多個命令叫復合命令
- 新建復合命令WrapperCommand,遵守協(xié)議
@interface WrapperCommand : NSObject<meryin_CommandProtocol>
- (instancetype)initWith:(NSMutableArray *)commands;
@end
@interface WrapperCommand()
@property(nonatomic, strong) NSMutableArray* commands;
@end
@implementation WrapperCommand
- (instancetype)initWith:(NSMutableArray *)commands
{
self = [super init];
if (self) {
self.commands = commands;
}
return self;
}
- (void)execute{
for (id<meryin_CommandProtocol> item in self.commands) {
[item execute];
}
}
- 請求者-復合命令管理器WrapperCommandManager
跟動態(tài)命令DynamicCommandManager類似,只有撤銷所有的方法不一樣
//撤銷所有
- (void)undoAll{
if (self.commands.count >0) {
NSLog(@"撤銷所有");
//撤銷
for (id <meryin_CommandProtocol> command in self.commands) {
[command execute];
}
//移除
[self.commands removeAllObjects];
}
}
3. 泛型命令
在定義的時候不需要指定類型,在使用的時候指定類型。c++里叫模版。
- 新建GenericsCommand泛型命令,接收者是動態(tài)的,可以持有任何接收者
//可以接受所有接收者
//T任意類型的標記
@interface GenericsCommand<T> : NSObject<meryin_CommandProtocol>
- (instancetype)initWith:(T)reciver block:(void(^)(T))commandBlock;
+(id<meryin_CommandProtocol>)createCommand:(T)reciver block:(void (^)(T))block;
@end
@interface GenericsCommand<T>()
@property(nonatomic, strong) T reciever;
@property(nonatomic, copy) void(^block)(T);
@end
@implementation GenericsCommand
//id 指向泛型類型的引用
- (instancetype)initWith:(id)reciver block:(void(^)(id))commandBlock
{
self = [super init];
if (self) {
self.reciever = reciver;
self.block = commandBlock;
}
return self;
}
//創(chuàng)建命令
+(id<meryin_CommandProtocol>)createCommand:(id)reciver block:(void (^)(id))block{
return [[GenericsCommand alloc]initWith:reciver block:block];
}
- (void)execute{
if (self.block) {
self.block(self.reciever);
}
}
- 泛型命令管理器,用泛型命令創(chuàng)建命令集合
@implementation GenericsCommandManager
- (instancetype)initWith:(Meryin_Reciever *)reciver{
self = [super init];
if (self) {
self.reciever = reciver;
self.commands = [NSMutableArray array];
}
return self;
}
-(void)toStart{
[self addCommand:@"start"];
[ self.reciever start];
}
- (void)toEnd{
[self addCommand:@"end"];
[self.reciever end];
}
- (void)addCommand:(NSString *)methodName{
//動態(tài)添加命令
GenericsCommand *command =[GenericsCommand createCommand:self.reciever block:^(Meryin_Reciever *block) {
SEL method = NSSelectorFromString(methodName);
//執(zhí)行回調
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[block performSelector:method];
#pragma clang diagnostic pop
}];
[self.commands addObject:command];
}
//撤銷所有
- (void)undoAll{
if (self.commands.count >0) {
NSLog(@"撤銷所有");
//撤銷
WrapperCommand *commands = [[WrapperCommand alloc]initWith:self.commands];
[commands execute];
//移除
[self.commands removeAllObjects];
}
}
4. 并發(fā)處理
- (void)addCommand:(NSString *)methodName{
dispatch_sync(self.queue, ^{
//動態(tài)添加命令
GenericsCommand *commnad = [GenericsCommand createCommand:self.reciever block:^(Meryin_Reciever *block) {
SEL method = NSSelectorFromString(methodName);
//執(zhí)行回調
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[block performSelector:method];
#pragma clang diagnostic pop
}];
[self.commands addObject:commnad];
});
}
5. 命令接口 Command用block
之前CommandProtocol用的是協(xié)議,現(xiàn)在用block,直接用管理器,隊列里保存的是block
- (void)addCommand:(NSString *)methodName{
dispatch_sync(self.queue, ^{
SEL method = NSSelectorFromString(methodName);
//動態(tài)添加命令
blcokCommand block = ^(Meryin_Reciever *reciver){
//執(zhí)行回調
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[reciver performSelector:method];
#pragma clang diagnostic pop
};
[self.commands addObject:block];
});
}
//撤銷第幾個 從0開始
- (void)undoByIndex:(NSInteger )index{
if (self.commands.count >0 && self.commands.count > index) {
NSLog(@"撤銷--%lu",index);
//撤銷
blcokCommand commandBlock= [self.commands objectAtIndex:index] ;
commandBlock(self.reciever);
//移除
[self.commands removeObjectAtIndex:index];
}
}
四 命令模式應用場景-購物車商品的添加刪減
命令模式的撤銷和其他業(yè)務邏輯分開,商品的增加和刪減可以和其他業(yè)務邏輯分開,可以減少controller的代碼量
分析角色:命令接口CommandProtocol,具體命令GenericsCommand,接收者ShoppingRecevier,請求者GenericsCommandManager
實際案例中命令保存時成對出現(xiàn),比如減少購物車商品的撤銷其實就是增加購物車商品
- (void)addGood:(NSString *)value withMode:(GoodsMode*)goods{
[self addCommand:@"reduceGood:withMode:" value:value model:goods];
[self.reciever addGood:value withMode:goods];
}
- (void)reduceGood:(NSString *)value withMode:(GoodsMode*)goods{
[self addCommand:@"addGood:withMode:" value:value model:goods];
[self.reciever reduceGood:value withMode:goods];
}
//保存命令
- (void)addCommand:(NSString *)methodName value:(NSString *)value model:(GoodsMode*)goods{
//動態(tài)添加命令
[self.commands addObject: [GenericsCommand createCommand:self.reciever block:^(ShoppingRecevier *reciver) {
SEL method = NSSelectorFromString(methodName);
//執(zhí)行回調
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if (reciver && method){
[reciver performSelector:method withObject:value withObject:goods];
}
#pragma clang diagnostic pop
}]];
}
