0%

命令模式(Command)

命令模式别名为动机(Action)、事务(Transaction),属于对象行为型模式。

在《设计模式:可复用面向对象软件的基础》中的介绍:

“将一个请求封装为一个对象,从而可以用不同的请求对客户进行参数化、对请求排队或者记录请求日志,以及支持可取消的操作”

即:方便于使用不同的请求、队列或日志来参数化其它对象,同时支持可撤销操作。


意图

有时候需要向某个对象发送一个请求,但:

  • 不知道接收该请求的具体对象(接收者)是谁;
  • 不知道具体处理过程,被请求到的操作是哪(几)个。

在命令模式中,设计好的一堆命令会放到一个“编排器”中,“编排器”规定了命令的执行顺序。
按照命令模式的开发流程,只需要知道程序运行中指定的具体请求接收者即可。

动机

  • 可使得请求的发送者和接收者之间实现完全的解耦;
  • 发送者和接收者之间没有直接联系:发送者只需要知道如何发送请求命令即可,其它一概不管。

结构

以上,可见命令模式包括以下角色:

Command

  • 抽象命令类
  • 声明执行操作的接口(execute())

ConcreteCommand

  • 具体命令类
  • 将一个接收者 Receiver 对象绑定于一个动作,调用 Receiver 相应的操作,以实现 execute()
  • 如没有绑定 Receiver 对象:则操作的逻辑在该具体命令类中定义

Invoker

  • 调用者
  • 定义某一个请求过来的时候,对应有一个命令执行该请求

Receiver

  • 接收者
  • 定义了多个实际的与不同的请求相关的操作
  • 任何类都可能成为一个接收者
  • 可有可无:视乎业务逻辑;如不定义接收者类:将操作逻辑上提到具体命令类

Client

  • 客户类
  • 通过调用调用者 Invoker 执行命令


实现

  • Client 和 Receiver 需要同时开发
  • 在开发过程中,随着具体命令 ConcreteCommand 的变化,Client 和 Receiver 分别需要不停地重构、改名
  • Receiver 是实际的执行类,执行与请求相关的操作
    • concreteCommand.execute() 调用 Receiver 对应的操作,然后 Client 通过调用 Involker 实例,执行 concreteCommand.execute()
  • Invoker 对象一般由一个或多个具体命令类构造而成
  • 如要实现可撤销功能(redo & undo),需要命令不按照调用执行,而是按照执行时的情况排序并执行


示例代码

调用者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Invoker {

private Command commandOne;
private Command commandTwo;

// 该调用者仅接收两个命令
public Invoker(Command commandOne, Command commandTwo) {
this.commandOne = commandOne;
this.commandTwo = commandTwo;
}

public void actionOne() {
commandOne.execute();
}

public void actionTwo() {
commandTwo.execute();
}
}

接收者:具体做事情的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Receiver {

public Receiver() {
//
}

public void actionOne() {
System.out.println("ActionOne has been taken.");
}

public void actionTwo() {
System.out.println("ActionTwo has been taken.");
}
}

命令类:间接或直接做事情的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public interface Command {
void execute();
}

public class ConcreteCommandOne implements Command {

private Receiver receiver;

public ConcreteCommandOne(Receiver receiver) {
this.receiver = receiver;
}

public void execute() {
receiver.actionOne();
}
}

public class ConcreteCommandTwo implements Command {

private Receiver receiver;

public ConcreteCommandTwo(Receiver receiver) {
this.receiver = receiver;
}

public void execute() {
receiver.actionTwo();
}
}

客户类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {

public static void main(String[] args) {
Receiver receiver = new Receiver();
Command commandOne = new ConcreteCommandOne(receiver);
Command commandTwo = new ConcreteCommandTwo(receiver);
Invoker invoker = new Invoker(commandOne, commandTwo);

// 客户完全不需要管执行到的具体命令和顺序
invoker.actionOne();
invoker.actionTwo();
}
}

Redo & Undo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ConcreteCommandOne implements Command {

private Receiver receiver;
private Receiver lastReceiver;

public ConcreteCommandOne(Receiver receiver) {
this.receiver = receiver;
}

public void execute() {
record();
receiver.actionOne();
}

public void record() {
// 记录状态
}

public void undo() {
// 恢复状态
}

public void redo() {
lastReceiver.actionOne();
}
}

具体可用 List,Queue,图等实现方式。


优缺点

优点:

  • 降低系统的耦合度
  • 新的命令可以很容易地加入到系统中
  • 可以比较容易地设计一个命令队列和宏命令(组合命令),即解决好一系列命令的安排
  • 可以方便地实现对请求的 Undo 和 Redo

缺点:

  • 可能会导致某些系统有过多的具体命令类:因为针对每一个命令都需要设计一个具体命令类
  • 因此某些系统可能需要大量具体命令类,这将影响命令模式的使用