面向对象设计的设计模式(十七):命令模式

定义

命令模式(Command Pattern):命令(或请求)被封装成对象。客户端将命令(或请求)对象先传递给调用对象。调用对象再把该命令(或请求)对象传给合适的,可处理该命令(或请求)的对象来做处理。

由定义可以看出,在命令模式中,命令被封装成了对象,而发送命令的客户端与处理命令的接收者中间被调用对象隔开了,这种设计的原因或者适用的场景是什么样的呢?

适用场景

在有些场景下,任务的处理可能不是需要立即执行的:可能需要记录(日至),撤销或重试(网络请求)。那么在这些场景下,如果任务的请求者和执行者是紧耦合状态下的话就可能会将很多其他执行策略的代码和立即执行的代码混合到一起。

这些其他执行策略,我们暂时称之为控制和管理策略,而如果我们如果想控制和管理请求,就需要:

  1. 把请求抽象出来
  2. 让另外一个角色来负责控制和管理请求的任务

因此命令模式就是为此场景量身打造的,它通过:

  1. 把请求封装成对象
  2. 使用调用者在客户端和请求处理者之间来做一个“拦截”,方便对请求对象做控制和管理。

现在我们清楚了命令模式的适用场景,下面看一下命令模式的成员和类图。

成员与类图

成员

不包括请求的发起者(客户端),命令模式共有四个成员:

  • 抽象命令类(Command):命令类负责声明命令的接口。
  • 具体命令类(Concrete Command):具体命令类负责实现抽象命令类声明的接口
  • 调用者(Invoker):调用者负责将具体命令类的实例传递给接收者
  • 接收者(Receiver):接收者负责处理命令

下面通过类图来看一下命令模式各个成员之间的关系:

模式类图

命令模式类图

代码示例

场景概述

模拟一个使用遥控器开灯和关灯的例子。

场景分析

在这个例子中,使用遥控器的人就是客户端,TA发起开启或关闭灯的命令给遥控器(调用者)。然后调用者将命令传递给接收者(灯)。

在这里,人是不直接接触灯的,开启和关闭的命令是通过遥控器来做的转发,最后传达给灯来执行。

下面我们看一下如何用代码来模拟该场景。

代码实现

首先我们创建接收者,灯类:

//================== Light.h ==================

@interface Light : NSObject

- (void)lightOn;

- (void)lightOff;

@end



//================== Light.m ==================

@implementation Light


- (void)lightOn{

    NSLog(@"Light on");
}


- (void)lightOff{

    NSLog(@"Light off");
}

@end

灯类声明并实现了两个接口:开灯接口和关灯接口,来让外部执行开灯和关灯的操作。

接着我们创建抽象命令类和具体命令类:

抽象命令类:

//================== Command.h ==================

@interface Command : NSObject

- (void)excute;

@end



//================== Command.m ==================

@implementation Command

@end

抽象命令类声明了一个执行命令的接口excute,这个接口由它的子类,也就是具体命令类来实现。

因为这里面只有开灯和关灯两种命令,所以我们创建两个具体命令类来继承上面的抽象命令类:

开灯命令CommandLightOn

//================== CommandLightOn.h ==================

@interface CommandLightOn : Command

- (instancetype)initWithLight:(Light *)light;

@end


//================== CommandLightOn.m ==================

@implementation CommandLightOn
{
    Light *_light;
}

- (instancetype)initWithLight:(Light *)light{

    self = [super init];
    if (self) {
        _light = light;
    }
    return self;
}

- (void)excute{

    [_light lightOn];
}

关灯命令CommandLightOff

//================== CommandLightOff.h ==================

@interface CommandLightOff : Command

- (instancetype)initWithLight:(Light *)light;

@end



//================== CommandLightOff.m ==================
@implementation CommandLightOff
{
    Light *_light;
}

- (instancetype)initWithLight:(Light *)light{

    self = [super init];
    if (self) {
        _light = light;
    }
    return self;
}

- (void)excute{

    [_light lightOff];
}

我们可以看到这两个具体命令类分别以自己的方式实现了它们的父类声明的excute接口。

最后我们创建链接客户端和接收者的调用者类,也就是遥控器类RemoteControl

//================== RemoteControl.h ==================

@interface RemoteControl : NSObject

- (void)setCommand:(Command *)command;

- (void)pressButton;

@end



//================== RemoteControl.m ==================

@implementation RemoteControl
{
    Command *_command;
}


- (void)setCommand:(Command *)command{

    _command = command;
}

- (void)pressButton{

    [_command excute];
}

@end

遥控器类使用set方法注入了具体命令类,并向外提供了pressButton这个方法来内部调用已传入的具体命令类的excute方法。

最后我们看一下客户端是如何操作这些类的:

//================== client ==================

//init Light and Command instance
//inject light instance into command instance
Light *light = [[Light alloc] init];
CommandLightOn *co = [[CommandLightOn alloc] initWithLight:light];

//set command on instance into remote control instance
RemoteControl *rm = [[RemoteControl alloc] init];
[rm setCommand:co];

//excute command(light  on command)
[rm pressButton];


//inject light instance into command off instance
CommandLightOff *cf = [[CommandLightOff alloc] initWithLight:light];

//change to off command
[rm setCommand:cf];

//excute command(light  close command)
[rm pressButton];

看一下日至输出:

[11851:1190777] Light on
[11851:1190777] Light off

从上面的代码可以看到,我们首先准备好具体命令类的实例,然后将其传递给遥控器类,最后触发遥控器的pressButton方法来间接触发light对象的相应操作。

下面看一下上面代码对应的类图。

代码对应的类图

命令模式代码示例类图

优点

  • 将命令的发起者和命令的执行者分离,降低系统的耦合度
  • 便于批量处理命令,比如日至队列的实现;便于命令的撤销或重试,比如网络请求等

缺点

  • 需要针对每一个命令创建一个命令对象。如果系统中的命令过多,会造成系统中存在大量的命令类,提高系统的复杂度。

iOS SDK 和 JDK中的应用

  • 在JDK中,java.lang.Runnable是使用命令模式的经典场景,Runnable接口可以作为抽象的命令,而实现了Runnable的线程即是具体的命令。

原文发布于微信公众号 - 程序员维他命(J_Knight_)

原文发表时间:2019-05-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券