行为型模式之命令模式

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令

场景

比如电脑开机,用户按钮开机按钮,等待就可以了,但是谁来处理的?怎么处理?经历了什么样的过程电脑才真正启动起来的?

上面所提的问题,我们平时关心吗?答案是肯定的,我们不关心,我们只需要按下开机按钮,等着电脑启动使用即可.那么上面的问题如果用编程来实现,怎么实现呢?

首先把上面的问题抽象描述一下:客户端只是想要发出命令或者请求,不关心请求的真正接收者是谁,也不关心具体如何实现,而且同一个请求的动作可以有不同的请求内容,当然具体的处理功能也不一样,请问该怎么实现?
要想实现一个请求命令可以被任意命令接受者执行,那么必须对二者进行解耦,让请求命令不必关心具体的命令接受者和命令执行的具体细节,只管发出命令,然后就可以被具体的命令接受者接受并执行。
那么如何实现二者的解耦呢?要让两者互相不知道,那么就必须引入一种中间量,这就是命令对象.它会把按钮和一个具体的命令对象关联起来,命令对象会暴露一个接口给按钮使用,同时每个命令对象都会和具体的命令接受者关联,调用命令接受者的功能。此时按下按钮,就会调用关联的命令对象的公开接口,然后命令对象内部在调用具体的命令接受者(主板)的公开方法,执行功能。

说了半天大家可能还是不太明白什么是命令模式?下面简单来说:
在很多时候,软件系统中,“行为请求者"和"行为实现者"通常呈现一种"紧耦合”.比如按钮的点击事件,虽然你有时候会把事件代码单独摘出来让一个实现类去实现,但实际上实现类和按钮之间是一种紧耦合.但某些场合,比如要对行为进行"记录,撤销/重做"等处理,这种无法抵御变化的紧耦合是不合适的.在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间
的松耦合。这就是命令模式(Command Pattern)。

模式结构和说明

在这里插入图片描述

Command:定义命令的接口,声明执行的方法
ConcreteCommand:命令接口实现对象,是"虚"的实现,通常会持有接收者,并调用接受者的功能来完成命令要执行的操作
Receiver:接收者,真正执行命令的对象.任何类都有可能成为一个接收者,只要它能够实现命令要求实现的相应功能
Invoker:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
Client:创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。

示例代码

//示例:改变view的明亮度

不使用模式的代码

先看看没有使用命令模式的代码

  1. UI绘制(添加两个按钮,一个增加亮度,一个减少亮度),代码忽略
  2. 创建事件处理者
@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
  1. 创建客户端接收器
    // 创建客户端接收器
    self.reciver           = [[Receiver alloc] init];
    [self.reciver bindView:self.view];
  1. 按钮时间处理
- (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,现在我想回到原始状态你怎么办?看到这你是不是蒙圈了?别急,咱们下面来用命令模式解决上面的问题

使用命令模式的代码

  1. UI绘制(添加三个按钮,一个增加亮度,一个减少亮度,一个撤销按钮),代码忽略

  2. 事件处理者(同上)

  3. 定义命令的接口,声明执行的方法

@protocol  CommandInterface <NSObject>

- (void)execute;

- (void)rollBack;

@end
  1. 定义命令接口实现对象,持有接收者,并调用接受者的功能来完成命令要执行的操作
//变亮命令(持有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
  1. 创建命令管理类,会持有命令对象(单例类)
@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
  1. 客户端使用
- (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. 发起请求的对象和真正实现的对象是解耦的

请求究竟由谁处理,如何处理,发起请求的对象是不知道的,也就是发起请求的对象和真正实现的对象是解耦的。发起请求的对象只管发出命令,其它的就不管了

Demo地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值