iOS架构设计模式之命令模式

一 基本概念

Demo地址

  1. 定义:它属于行为型模式,将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。降低耦合度。
  2. 使用场景:应用程序支持撤销和恢复;记录请求日志,当系统故障时可以重新被执行;想用对象参数化一个动作以执行操作并且用不同命令来替换回调函数。
  3. 角色划分:
    接收者-> Reciever
    命令接口-> Command(CommandProtocol协议)
    具体命令-> ConcrateCommand
    请求者-> Invoker:持有命令对象的引用 然后执行对象的命令
    Client类:最终的客户端调用类。


二 基础案例

  1. CommandProtocol
//命令接口-协议
@protocol Meryin_CommandProtocol<NSObject>
//操作->具体实现方法
-(void)execute;
@end
  1. 接收者Reciever实现具体方法功能
//接收者
@interface Meryin_Reciever : NSObject
//具体方法
-(void)start;
@end

@implementation Meryin_Reciever
-(void)start{
    NSLog(@"接收者实现具体方法的功能");
}
@end
  1. ConcrateCommand 遵守协议,持有接收者,实现协议具体方法,调用具体逻辑
//具体命令-> ConcrateCommand
//遵守协议,实现协议具体方法
@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
  1. Invoker 请求者,持有ConcrateCommand的引用,执行ConcrateCommand遵守协议的方法;保存操作记录支持回退撤销。

@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];
    }
}
  1. Client 最后调用
 //创建接收者
        Meryin_Reciever  *reciver = [[Meryin_Reciever alloc]init];
        //创建命令(分离)->解藕和
        Meryin_ConcrateCommand *command = [[Meryin_ConcrateCommand alloc]initWithReciever:reciver];
        //创建请求者
        Meryin_Invoker  *invoker = [[Meryin_Invoker alloc]initWith:command reciever:reciver];
        [invoker toStart];
        [invoker toStart];
        [invoker undoLastOne];

三 基础命令模式的一步步优化

1. 动态命令

以上创建的命令模式,当命令过多时,类就会太多,不好管理,如果使用动态调用,用Block回调,不需要新建各种命令类,用Block实现一个命令类里面可以动态创建多个命令。

  • 创建动态命令类DynamicCommand,持有block,可以动态创建命令实现接收者的各种方法。
typedef void(^dynamicBlcok)(Meryin_Reciever *);
//解决方法:用block实现
@interface DynamicCommand : NSObject<meryin_CommandProtocol>
- (instancetype)initWith:(Meryin_Reciever *)reciver block:(dynamicBlcok)block;
//创建命令
+(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);
    }
}
//创建命令
+(id<meryin_CommandProtocol>)createCommand:(Meryin_Reciever *)reciver block:(dynamicBlcok)block{
    return [[DynamicCommand alloc]initWith:reciver block:block];
}
  • 动态命令的请求者,动态添加命令,实现接收者方法的回调
@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{
    //动态添加命令
   DynamicCommand *command =[DynamicCommand createCommand:self.reciever block:^(Meryin_Reciever *block) {
   SEL method = NSSelectorFromString(methodName);
        //执行回调
#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客户端调用
//动态命令
        Meryin_Reciever  *reciver1 = [[Meryin_Reciever alloc]init];
        DynamicCommandManager  *manager1 = [[DynamicCommandManager alloc]initWith:reciver1];
        [manager1 toEnd];
        [manager1 toStart];
        [manager1 toStart];
        [manager1 toEnd];
        [manager1 undoByIndex:2];

命令模式并不去实现具体业务逻辑,只用于记录撤销等。具体撤销逻辑由Client决定,命令模式只是保存操作。

2. 复合命令

执行多个命令叫复合命令

  • 新建复合命令WrapperCommand,遵守协议
@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
    跟动态命令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任意类型的标记
@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;
}
//创建命令
+(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);
    }
}
  • 泛型命令管理器,用泛型命令创建命令集合
@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{
    //动态添加命令
   GenericsCommand *command =[GenericsCommand createCommand:self.reciever block:^(Meryin_Reciever *block) {
    SEL method = NSSelectorFromString(methodName);
        //执行回调
#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. 并发处理
- (void)addCommand:(NSString *)methodName{
    dispatch_sync(self.queue, ^{
        //动态添加命令
        GenericsCommand *commnad = [GenericsCommand createCommand:self.reciever block:^(Meryin_Reciever *block) {
        SEL method = NSSelectorFromString(methodName);
            //执行回调
#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用的是协议,现在用block,直接用管理器,队列里保存的是block

- (void)addCommand:(NSString *)methodName{
    dispatch_sync(self.queue, ^{
        SEL method = NSSelectorFromString(methodName);
        //动态添加命令
        blcokCommand block = ^(Meryin_Reciever *reciver){
            //执行回调
#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];
    }
}

四 命令模式应用场景-购物车商品的添加删减

命令模式的撤销和其他业务逻辑分开,商品的增加和删减可以和其他业务逻辑分开,可以减少controller的代码量
分析角色:命令接口CommandProtocol,具体命令GenericsCommand,接收者ShoppingRecevier,请求者GenericsCommandManager
实际案例中命令保存时成对出现,比如减少购物车商品的撤销其实就是增加购物车商品

- (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{
  
    //动态添加命令
   [self.commands addObject: [GenericsCommand createCommand:self.reciever block:^(ShoppingRecevier *reciver) {
          SEL method = NSSelectorFromString(methodName);
        //执行回调
#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
   }]];
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,393评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,104评论 1 32
  • OC语言基础 1.类与对象 类方法 OC的类方法只有2种:静态方法和实例方法两种 在OC中,只要方法声明在@int...
    奇异果好补阅读 4,276评论 0 11
  • 2018.6.19 星期二 晴 亲子日记第192天 今天去看望了陈老师,见到孩子们老师特别的高兴,给孩子们拿出很多...
    涓涓流水_672f阅读 114评论 0 1
  • 从五月份开始投资数字货币,1个多月过去了,自己积累了一些微薄的经验,拿出来和战友们分享一下。 数字货币属于高风险高...
    MAXWELLL阅读 1,769评论 1 2