设计模式系列-命令模式

每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方接收到请求,并执行操作。命令模式允许请求的一方和接收的一方独立开来,==使得请求的一方不必知道接收请求的一方的接口==,更不必知道请求是怎么被接收、以及操作是否被执行、何时被执行、怎么被执行的。

主要是调用者与接收者隔离,调用者调用具体的命令,命令去调用具体的接收者执行。

image

  • Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作
  • ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()方法时,将调用接收者对象的相关操作(Action)。[有一个Receiver作为成员变量]
  • Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
  • Receiver(接收者):接收者请求与执行相关的操作,它具体实现对请求的业务处理。

抽象命令角色类

1
2
3
4
5
6
public interface Command {
/**
* 执行方法
*/
void execute();
}

具体命令角色类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ConcreteCommand implements Command {
/**
* 持有相应的接收者对象
*/
private Receiver receiver = null;
/**
* 构造方法
* @param receiver
*/
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
//通常会转调接收者的形影方法,让接收者来真正执行功能
receiver.action();
}
}

接收者角色类

1
2
3
4
5
6
7
8
public class Receiver {
/**
* 真正执行命令相应的操作
*/
public void action() {
System.out.println("执行操作");
}
}

请求者角色类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Invoker {
/**
* 持有命令对象
*/
private Command command = null;
/**
* 构造方法
* @param command
*/
public Invoker(Command command) {
this.command = command;
}
/**
* 行动方法
*/
public void action() {
command.execute();
}
}

调用

1
2
3
4
5
6
7
8
9
10
11
12
public class Client {
public static void main(String[] args) {
//创建接收者
Receiver receiver = new Receiver();
//创建命令对象,设定其接收者
Command command = new ConcreteCommand(receiver);
//创建请求者,把命令对象设置进去,无需关注真的执行者是谁。
Invoker invoker = new Invoker(command);
//执行方法
invoker.action();
}
}

实例

下面以电视机的开、关命令为例,进行讲解:

角色:

  • 接收者—电视
  • 命令— 开、关
  • 调用者— 遥控器
  • 客户端—人
1
2
3
4
5
6
7
8
9
/**
* 抽象命令角色类
*/
public interface Command {
/**
* 执行方法
*/
void execute();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 接收者角色类 -- 电视
*/
public class TV {
/**
* 打开方法
*/
public void on(){
System.out.println("电视打开");
}
/**
* 关闭方法
*/
public void off(){
System.out.println("电视关闭");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 具体命令角色类 -- 电视打开命令
*/
public class TVOnCommand implements Command {

private TV tv;


public TVOnCommand(TV tv) {
this.tv = tv;
}

@Override
public void execute() {
tv.on();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 具体命令角色类 -- 电视关闭命令
*/
public class TVOffCommand implements Command {
private TV tv;
public TVOffCommand(TV tv){
this.tv = tv;
}

@Override
public void execute() {
tv.off();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 请求者角色类 -- 遥控器Invoker
*/
public class RemoteControl {


private Command onCommand;

private Command offCommand;


public RemoteControl(Command onCommand, Command offCommand) {
this.onCommand = onCommand;
this.offCommand = offCommand;
}

public void turnOn(){
onCommand.execute();
}

public void turnOff(){
offCommand.execute();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 客户端角色类
* @author Json
*/
public class Client {
public static void main(String[] args) {
//创建接收者
TV receiver = new TV();
//创建命令对象,设定它的接收者
Command on_command = new TVOnCommand(receiver);
//创建命令对象,设定它的接收者
Command off_command = new TVOffCommand(receiver);
//命令控制对象Invoker,把命令对象通过构造方法设置进去 或者 增加set方法set进去是一样的(setCommand方法未写)
RemoteControl invoker = new RemoteControl(on_command,off_command);
//执行方法 -- 打开电视
invoker.turnOn();
//执行方法 -- 关闭电视
invoker.turnOff();
}
}

优缺点

优点:

  • 命令模式将行为调用者和接收者,降低程序的耦合,便于程序扩展;
  • 命令模式将行为的具体实现封装起来,客户端无需关心行为的具体实现;
  • 命令模式可为多种行为提供统一的调用入口,便于程序对行为的管理和控制;(组合命令)

缺点:

  • 使用命令模式的话,不用管命令多简单,都需要写一个命令类来封装,使用命令模式可能会导致系统有过多的具体命令类;

扩展

命令队列

主要差别在于invoker中存在一个队列,客户端将命令交给invoker后,invoker将命令保存在队列中,客户端调用call的时候统一执行(针对队列中的命令什么时候执行可以自定义。)

核心代码

带有队列的Invoker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Invoker {    
private CommandQueue commandQueue; //维持一个CommandQueue对象的引用

//构造注入
public Invoker(CommandQueue commandQueue) {
this. commandQueue = commandQueue;
}

//设值注入
public void setCommandQueue(CommandQueue commandQueue) {
this.commandQueue = commandQueue;
}

//调用CommandQueue类的execute()方法
public void call() {
commandQueue.execute();
}
}

队列的详细实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CommandQueue {    
//定义一个ArrayList来存储命令队列
private ArrayList<Command> commands = new ArrayList<Command>();

public void addCommand(Command command) {
commands.add(command);
}

public void removeCommand(Command command) {
commands.remove(command);
}

//循环调用每一个命令对象的execute()方法
public void execute() {
for (Object command : commands) {
((Command)command).execute();
}
}
}

实例讲解

还是电视的例子,这次的遥控器比较奇怪,是一个batch模式的遥控器

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

public static void main(String[] args) {
TV receiver = new TV();
Command onCommand = new TVOnCommand(receiver);

Command offCommand = new TVOffCommand(receiver);

CommandQueue commandQueue = new CommandQueue();
commandQueue.addCommand(onCommand);
commandQueue.addCommand(onCommand);
commandQueue.addCommand(offCommand);
BatchRemoteControl batchRemoteControl = new BatchRemoteControl(commandQueue);

batchRemoteControl.call();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 请求者角色类 -- 遥控器Invoker
*/
public class BatchRemoteControl {

private CommandQueue commandQueue;

public BatchRemoteControl(CommandQueue commandQueue) {
this.commandQueue = commandQueue;
}

public void call() {
this.commandQueue.execute();
}
}
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
30
31
32
33

public class CommandQueue {

/**
* 用来存储命令对象的队列
*/
private static List<Command> commands = new ArrayList<Command>();

public void addCommand(Command command) {
commands.add(command);
}

/**
* 获取一个指令执行
*/
public synchronized static Command getOneCommand() {
Command cmd = null;
if (commands.size() > 0) {
//取出队列的第一个,因为是约定的按照加入的先后来处理
cmd = commands.get(0);
//同时从队列里面取掉这个命令对象
commands.remove(0);
}
return cmd;
}

//循环调用每一个命令对象的execute()方法
public void execute() {
for (Command command : commands) {
command.execute();
}
}
}

宏命令

宏命令是讲多个命令组合成一个命令,感觉就是组合+重命名吧。具体实现是新增一个command的实现,这个实现里头是包含一个对列,存放对应的多个细粒度执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 宏命令接口
*/
public interface MacroCommand extends Command {
/**
* 宏命令的管理方法
* 添加命令
*/
void addCommand(Command command);
/**
* 宏命令的管理方法
* 移除命令
*/
void removeCommand(Command command);
}
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
/**
* 宏命令接口实现
*/
public class ComputerMacroCommand implements MacroCommand {
private List<Command> commands;
//无参构造方法
public ComputerMacroCommand(){
commands = new ArrayList<Command>();
}

@Override
public void addCommand(Command command){
commands.add(command);
}

@Override
public void removeCommand(Command command){
commands.remove(command);
}

@Override
public void execute(){
for (int i=0; i < commands.size(); i++){
commands.get(i).execute();
}
}

}

实例讲解

如果想要实现一个宏命令,他的作用是先打开电视再关闭电视。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 宏命令接口
*/
public interface MacroCommand extends Command {
/**
* 宏命令的管理方法
* 添加命令
*/
void addCommand(Command command);
/**
* 宏命令的管理方法
* 移除命令
*/
void removeCommand(Command command);
}
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

public class OnAndOffCommand implements MacroCommand {

private List<Command> commands;

//无参构造方法
public OnAndOffCommand() {
commands = new ArrayList<Command>();
}

@Override
public void addCommand(Command command){
commands.add(command);
}

@Override
public void removeCommand(Command command){
commands.remove(command);
}

@Override
public void execute(){
for (int i=0; i < commands.size(); i++){
commands.get(i).execute();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AClient {

public static void main(String[] args) {
TV receiver = new TV();
Command onCommand = new TVOnCommand(receiver);

Command offCommand = new TVOffCommand(receiver);

OnAndOffCommand onAndOffCommand = new OnAndOffCommand();

onAndOffCommand.addCommand(onCommand);
onAndOffCommand.addCommand(offCommand);

onAndOffCommand.execute();
}
}

有没有觉得跟命令对列有点像?都是有一个List对列。

差别在于命令队列,队列是存放在invoker,调用者存放请求者请求过来的命令,可以做限流等操作。

而宏命令,是一个命令的实例,他的队列是存放组成宏的组成。

Reference

https://www.cnblogs.com/JsonShare/p/7206607.html

https://www.cnblogs.com/java-my-life/archive/2012/06/01/2526972.html