设计模式-创建型-命令模式(Command)
参考文献
- https://www.oodesign.com/command-pattern
- https://refactoringguru.cn/design-patterns/command
- https://java-design-patterns.com/patterns/command/
命令模式
- 它可将请求转换为一个包含与请求相关的所有信息的独立对象. 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作.
组件
-
Command
- 接口通常仅声明一个执行命令的方法. -
ConcreteCommand
- 会实现各种类型的请求. 具体命令自身并不完成工作, 而是会将调用委派给一个业务逻辑对象. 但为了简化代码, 这些类可以进行合并.接收对象执行方法所需的参数可以声明为具体命令的成员变量. 你可以将命令对象设为不可变, 仅允许通过构造函数对这些成员变量进行初始化.
-
Client
- 会创建并配置具体命令对象. 客户端必须将包括接收者实体在内的所有请求参数传递给命令的构造函数. 此后, 生成的命令就可以与一个或多个发送者相关联了. -
Invoker
- 类负责对请求进行初始化, 其中必须包含一个成员变量来存储对于命令对象的引用. 发送者触发命令, 而不向接收者直接发送请求. 注意, 发送者并不负责创建命令对象: 它通常会通过构造函数从客户端处获得预先生成的命令. -
Receiver
- 类包含部分业务逻辑. 几乎任何对象都可以作为接收者. 绝大部分命令只处理如何将请求传递到接收者的细节, 接收者自己会完成实际的工作.
使用场景
- 需要将请求发送者和接收者解耦:命令模式可以将请求封装成对象,使得发送者和接收者之间的耦合度降低.发送者不需要知道请求的具体执行细节,只需要调用命令对象的执行方法即可.
- 需要支持撤销和重做操作:命令模式可以在命令对象中保存请求的状态,在需要撤销或重做操作时,可以通过保存的状态信息恢复到之前的状态.
- 需要支持命令的队列与日志记录:命令模式可以将请求对象存储在队列中,按照一定的顺序来执行请求.同时,也可以在执行命令的同时记录日志,方便后续跟踪和分析.
- 需要实现回调和事件驱动的系统:命令模式可以通过命令对象来实现回调机制和事件驱动的系统.发送者将命令对象传递给接收者,接收者在适当的时候调用命令对象的执行方法.
- 需要支持扩展性和灵活性:命令模式可以通过新增具体命令类来扩展系统功能,而无需修改现有的源代码.同时,也可以通过组合不同的命令对象来实现复杂的操作.
总结
- 命令模式适用于需要将请求封装成对象,并且需要对请求进行参数化、排队或记录日志的情况.它能够帮助我们实现更松散耦合、可扩展和可维护的代码结构.常见的应用场景包括遥控器按钮、菜单项操作、操作日志、事务处理等
实现方式
-
声明命令接口:创建一个命令接口,该接口中只包含一个执行方法,例如
Command
接口,并在接口中定义一个execute()
方法.1
2
3public interface Command {
void execute();
} -
实现具体命令类:根据具体的业务需求,创建实现了命令接口的具体命令类,例如
ConcreteCommandA
和ConcreteCommandB
.这些具体命令类需要实现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
25public class TurnOnCommand implements Command {
private Television television;
public TurnOnCommand(Television television) {
this.television = television;
}
public void execute() {
television.turnOn();
}
}
public class TurnOffCommand implements Command {
private Television television;
public TurnOffCommand(Television television) {
this.television = television;
}
public void execute() {
television.turnOff();
}
} -
创建接收者类:创建一个接收者类,该类负责实际执行命令的操作,例如
Receiver
类.在该类中定义具体的操作方法.1
2
3
4
5
6
7
8
9public class Television {
public void turnOn() {
System.out.println("电视已打开");
}
public void turnOff() {
System.out.println("电视已关闭");
}
} -
创建发送者类:创建一个发送者类,该类担任发送者的职责,并将命令对象与接收者关联起来.发送者类可以包含一个成员变量来保存命令对象,以及相应的方法来触发执行命令的操作.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class RemoteControl {
private Command powerOnCommand;
private Command powerOffCommand;
public void setPowerOnCommand(Command command) {
this.powerOnCommand = command;
}
public void setPowerOffCommand(Command command) {
this.powerOffCommand = command;
}
public void pressPowerOnButton() {
powerOnCommand.execute();
}
public void pressPowerOffButton() {
powerOffCommand.execute();
}
} -
客户端使用:客户端代码需要按照以下顺序来初始化对象:
- 创建接收者对象.
- 创建具体命令对象,并将其关联到接收者对象.
- 创建发送者对象,并将其与具体命令对象关联.
-
调用命令:在客户端中,通过发送者对象调用执行命令的方法,即发送者对象执行命令的方法,而不是直接将请求发送给接收者.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Client {
public static void main(String[] args) {
Television television = new Television();
Command powerOnCommand = new TurnOnCommand(television);
Command powerOffCommand = new TurnOffCommand(television);
RemoteControl remoteControl = new RemoteControl();
remoteControl.setPowerOnCommand(powerOnCommand);
remoteControl.setPowerOffCommand(powerOffCommand);
// 模拟按下电视遥控器的开关按钮
remoteControl.pressPowerOnButton(); // 输出:电视已打开
remoteControl.pressPowerOffButton(); // 输出:电视已关闭
}
}