Java 中的命令模式:赋能灵活的命令执行
大约 4 分钟
也称为
- 操作
- 事务
命令设计模式的意图
命令设计模式是 Java 编程中使用的一种行为模式。它将请求封装为对象,允许使用队列、请求和操作对客户端进行参数化。此模式还支持可撤销的操作,从而增强了管理和执行命令的灵活性。
命令模式的详细解释以及现实世界示例
现实世界示例
想象一个智能家居系统,您可以在其中通过一个中央应用程序控制灯、恒温器和安全摄像头等设备。操作这些设备的每个命令都封装为一个对象,使系统能够排队、顺序执行以及在必要时撤销命令。这种方法将控制逻辑与设备实现分离,允许轻松添加新设备或功能,而无需更改核心应用程序。这种灵活性和功能说明了命令设计模式在 Java 编程中的实际应用。
简单来说
将请求存储为命令对象允许在以后执行操作或撤销操作。
维基百科说
在面向对象编程中,命令模式是一种行为设计模式,其中一个对象用于封装执行操作或在稍后时间触发事件所需的所有信息。
Java 中命令模式的编程示例
在命令模式中,对象用于封装执行操作或在稍后时间触发事件所需的所有信息。此模式特别适用于在应用程序中实现撤销功能。
在我们的示例中,一个巫师
对哥布林
施法。每个法术都是一个命令对象,可以执行和撤销,展示了 Java 中命令模式的核心原则。法术依次对哥布林执行。第一个法术缩小了哥布林,第二个法术使他隐形。然后巫师逐个反转法术。这里每个法术都是一个可以撤销的命令对象。
让我们从巫师
类开始。
@Slf4j
public class Wizard {
private final Deque<Runnable> undoStack = new LinkedList<>();
private final Deque<Runnable> redoStack = new LinkedList<>();
public Wizard() {
}
public void castSpell(Runnable runnable) {
runnable.run();
undoStack.offerLast(runnable);
}
public void undoLastSpell() {
if (!undoStack.isEmpty()) {
var previousSpell = undoStack.pollLast();
redoStack.offerLast(previousSpell);
previousSpell.run();
}
}
public void redoLastSpell() {
if (!redoStack.isEmpty()) {
var previousSpell = redoStack.pollLast();
undoStack.offerLast(previousSpell);
previousSpell.run();
}
}
@Override
public String toString() {
return "Wizard";
}
}
接下来,我们有哥布林
,他是法术的目标
。
@Slf4j
@Getter
@Setter
public abstract class Target {
private Size size;
private Visibility visibility;
public void printStatus() {
LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility());
}
public void changeSize() {
var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL;
setSize(oldSize);
}
public void changeVisibility() {
var visible = getVisibility() == Visibility.INVISIBLE
? Visibility.VISIBLE : Visibility.INVISIBLE;
setVisibility(visible);
}
}
public class Goblin extends Target {
public Goblin() {
setSize(Size.NORMAL);
setVisibility(Visibility.VISIBLE);
}
@Override
public String toString() {
return "Goblin";
}
}
最后,我们可以展示巫师
施法的完整示例。
public static void main(String[] args) {
var wizard = new Wizard();
var goblin = new Goblin();
goblin.printStatus();
wizard.castSpell(goblin::changeSize);
goblin.printStatus();
wizard.castSpell(goblin::changeVisibility);
goblin.printStatus();
wizard.undoLastSpell();
goblin.printStatus();
wizard.undoLastSpell();
goblin.printStatus();
wizard.redoLastSpell();
goblin.printStatus();
wizard.redoLastSpell();
goblin.printStatus();
}
这是程序输出
20:13:38.406 [main] INFO com.iluwatar.command.Target -- Goblin, [size=normal] [visibility=visible]
20:13:38.409 [main] INFO com.iluwatar.command.Target -- Goblin, [size=small] [visibility=visible]
20:13:38.409 [main] INFO com.iluwatar.command.Target -- Goblin, [size=small] [visibility=invisible]
20:13:38.409 [main] INFO com.iluwatar.command.Target -- Goblin, [size=small] [visibility=visible]
20:13:38.409 [main] INFO com.iluwatar.command.Target -- Goblin, [size=normal] [visibility=visible]
20:13:38.409 [main] INFO com.iluwatar.command.Target -- Goblin, [size=small] [visibility=visible]
20:13:38.409 [main] INFO com.iluwatar.command.Target -- Goblin, [size=small] [visibility=invisible]
何时在 Java 中使用命令模式
当您需要使用操作参数化对象、支持撤销操作或围绕基于基本操作的高级操作构建系统时,命令设计模式适用。它通常用于 GUI 按钮、数据库事务和宏录制。
当您想使用命令模式时
- 使用操作参数化对象以执行操作,提供面向对象的替代方法,以替代过程语言中的回调。命令可以注册并在稍后执行。
- 在不同的时间指定、排队和执行请求,允许命令独立于原始请求存在,甚至可以跨进程传输。
- 支持撤销功能,其中命令的执行操作存储状态,并包含一个取消执行操作来撤销之前的操作。这允许通过维护历史列表来实现无限的撤销和重做功能。
- 记录更改以在系统崩溃后重新应用它们。通过向命令接口添加加载和存储操作,您可以维护更改的持久日志,并通过从该日志中重新加载和重新执行命令来恢复。
- 围绕基于基本操作的高级操作构建系统,这在基于事务的系统中很常见。命令模式通过提供用于调用和扩展操作的通用接口来对事务进行建模。
- 保留请求的历史记录。
- 实现回调功能。
- 实现撤销功能。
Java 中命令模式的现实世界应用
- 桌面应用程序中的 GUI 按钮和菜单项。
- 支持回滚的数据库系统和事务性系统中的操作。
- 文本编辑器和电子表格等应用程序中的宏录制。
- java.lang.Runnable
- org.junit.runners.model.Statement
- Netflix Hystrix
- javax.swing.Action
命令模式的优缺点
优点
- 将调用操作的对象与知道如何执行操作的对象解耦。
- 添加新命令很容易,因为您不必更改现有类。
- 您可以将一组命令组装成一个复合命令。
缺点
- 增加了每个单独命令的类数。
- 可以通过在发送方和接收方之间添加多个层来使设计变得复杂。