命令模式 (Command Pattern)
概述
命令模式是一种行为型设计模式,它将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
意图
- 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化
- 对请求排队或记录请求日志,以及支持可撤销的操作
适用场景
- 需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
- 需要在不同的时间指定请求、将请求排队和执行请求
- 需要支持命令的撤销(Undo)和恢复(Redo)操作
- 需要将一组操作组合在一起,即支持宏命令
结构
┌─────────────┐ ┌─────────────┐
│ Client │──────────>│ Invoker │
├─────────────┤ ├─────────────┤
│ │ │ - command │
└─────────────┘ │ + setCommand() │
│ + executeCommand() │
└─────────────┘
▲
│
┌─────────────┐ ┌─────────────┐
│ Command │<─────────│ Receiver │
├─────────────┤ ├─────────────┤
│ + execute() │ │ + action() │
│ + undo() │ └─────────────┘
└─────────────┘
▲
│
┌─────────────┐
│ConcreteCommand│
├─────────────┤
│ - receiver │
│ + execute() │
│ + undo() │
└─────────────┘
参与者
- Command:声明执行操作的接口
- ConcreteCommand:将一个接收者对象绑定于一个动作,调用接收者相应的操作,以实现Execute
- Client:创建一个具体命令对象并设定它的接收者
- Invoker:要求该命令执行这个请求
- Receiver:知道如何实施与执行一个请求相关的操作
示例代码
下面是一个完整的命令模式示例,以遥控器控制电器为例:
java
// Receiver - 接收者
public class Light {
private String location;
public Light(String location) {
this.location = location;
}
public void on() {
System.out.println(location + " 灯已打开");
}
public void off() {
System.out.println(location + " 灯已关闭");
}
}
public class Stereo {
private String location;
public Stereo(String location) {
this.location = location;
}
public void on() {
System.out.println(location + " 音响已打开");
}
public void off() {
System.out.println(location + " 音响已关闭");
}
public void setCD() {
System.out.println(location + " 音响已设置CD");
}
public void setVolume(int volume) {
System.out.println(location + " 音响音量设置为 " + volume);
}
}
// Command - 命令接口
public interface Command {
void execute();
void undo();
}
// ConcreteCommand - 具体命令1
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
// ConcreteCommand - 具体命令2
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
// ConcreteCommand - 具体命令3
public class StereoOnWithCDCommand implements Command {
private Stereo stereo;
public StereoOnWithCDCommand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void execute() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
@Override
public void undo() {
stereo.off();
}
}
// ConcreteCommand - 具体命令4
public class StereoOffCommand implements Command {
private Stereo stereo;
public StereoOffCommand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void execute() {
stereo.off();
}
@Override
public void undo() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
}
// ConcreteCommand - 空命令
public class NoCommand implements Command {
@Override
public void execute() {
System.out.println("什么也不做");
}
@Override
public void undo() {
System.out.println("什么也不做");
}
}
// Invoker - 调用者
public class RemoteControl {
private Command[] onCommands;
private Command[] offCommands;
private Command undoCommand;
public RemoteControl() {
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
public void undoButtonWasPushed() {
undoCommand.undo();
}
}
// Client - 客户端
public class Client {
public static void main(String[] args) {
// 创建接收者
Light livingRoomLight = new Light("客厅");
Light kitchenLight = new Light("厨房");
Stereo stereo = new Stereo("客厅");
// 创建命令
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);
LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);
StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo);
StereoOffCommand stereoOff = new StereoOffCommand(stereo);
// 创建调用者
RemoteControl remoteControl = new RemoteControl();
// 设置命令
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
remoteControl.setCommand(2, stereoOnWithCD, stereoOff);
// 测试
System.out.println("------ 测试客厅灯 ------");
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
System.out.println(remoteControl.undoButtonWasPushed());
System.out.println("\n------ 测试厨房灯 ------");
remoteControl.onButtonWasPushed(1);
remoteControl.offButtonWasPushed(1);
System.out.println(remoteControl.undoButtonWasPushed());
System.out.println("\n------ 测试音响 ------");
remoteControl.onButtonWasPushed(2);
remoteControl.offButtonWasPushed(2);
System.out.println(remoteControl.undoButtonWasPushed());
}
}
另一个示例 - 宏命令
java
// MacroCommand - 宏命令
public class MacroCommand implements Command {
private Command[] commands;
public MacroCommand(Command[] commands) {
this.commands = commands;
}
@Override
public void execute() {
for (int i = 0; i < commands.length; i++) {
commands[i].execute();
}
}
@Override
public void undo() {
for (int i = commands.length - 1; i >= 0; i--) {
commands[i].undo();
}
}
}
// Client - 客户端
public class Client {
public static void main(String[] args) {
// 创建接收者
Light light = new Light("客厅");
Stereo stereo = new Stereo("客厅");
TV tv = new TV("客厅");
// 创建命令
Command lightOn = new LightOnCommand(light);
Command stereoOn = new StereoOnWithCDCommand(stereo);
Command tvOn = new TVOnCommand(tv);
Command lightOff = new LightOffCommand(light);
Command stereoOff = new StereoOffCommand(stereo);
Command tvOff = new TVOffCommand(tv);
// 创建宏命令
Command[] partyOn = {lightOn, stereoOn, tvOn};
Command[] partyOff = {lightOff, stereoOff, tvOff};
MacroCommand partyOnMacro = new MacroCommand(partyOn);
MacroCommand partyOffMacro = new MacroCommand(partyOff);
// 创建调用者
RemoteControl remoteControl = new RemoteControl();
// 设置宏命令
remoteControl.setCommand(0, partyOnMacro, partyOffMacro);
// 测试宏命令
System.out.println("------ 测试派对模式 ------");
remoteControl.onButtonWasPushed(0);
System.out.println();
remoteControl.offButtonWasPushed(0);
System.out.println();
remoteControl.undoButtonWasPushed();
}
}
优缺点
优点
- 降低系统耦合度。将请求调用者和接收者解耦
- 新的命令可以很容易地加入到系统中
- 可以比较容易地设计一个命令队列或宏命令(组合命令)
- 可以方便地实现对请求的Undo和Redo
缺点
- 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用
相关模式
- 组合模式:命令模式可以使用组合模式来实现宏命令
- 备忘录模式:命令模式可以使用备忘录模式来支持可撤销的操作
- 策略模式:命令模式和策略模式都封装了算法,但命令模式还封装了调用者与接收者之间的关系
实际应用
- GUI中的按钮点击事件
- 文本编辑器中的撤销/重做功能
- 数据库事务中的回滚操作
- Java中的Runnable接口
- Swing中的Action接口
- 队列和栈中的操作
命令队列
命令模式可以很容易地实现命令队列,将命令对象放入队列中,然后依次执行:
java
import java.util.LinkedList;
import java.util.Queue;
public class CommandQueue {
private Queue<Command> queue = new LinkedList<>();
public void addCommand(Command command) {
queue.offer(command);
}
public void executeCommands() {
while (!queue.isEmpty()) {
Command command = queue.poll();
command.execute();
}
}
}
注意事项
- 命令模式中的命令对象应该保持轻量级,避免包含过多的状态
- 命令模式中的撤销操作需要额外的存储空间来保存状态
- 命令模式中的命令对象应该是可序列化的,以便于持久化和网络传输
- 命令模式中的命令对象应该是线程安全的,特别是在多线程环境中使用时