命令模式(Command Pattern)是一种行为型设计模式。它将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。
简单来说,命令模式就像是一个餐厅的点菜系统。顾客(客户端)发出点菜的请求(命令),服务员(调用者)接收这个请求并将其记录为一个订单(命令对象),然后厨房(接收者)根据这个订单进行烹饪。这里的订单就是封装了顾客请求的对象。
主要特性:
- 封装请求: 将一个请求封装成一个对象,从而允许你使用不同的请求对客户进行参数化。
- 解耦发送者和接收者: 发送者(调用方)与接收者(执行具体操作的对象)之间是完全解耦的,它们通过命令对象进行交互。
- 支持撤销/重做: 可以很容易地添加撤销或重做的功能,因为每个命令都保存了足够的信息来实现这些功能。
- 命令队列: 容易实现命令队列和日志记录,使得系统能够处理延迟执行或异步执行的命令。
一、工作中的应用场景
- 图形用户界面(GUI)应用:在 GUI 应用中,菜单选项和按钮的操作通常是使用命令模式实现的。例如,在一个文字处理软件中,“新建文档”“打开文件”“保存文件” 等菜单选项都是命令。当用户点击 “保存文件” 按钮(调用者)时,就会执行一个 “保存文件” 命令(命令对象)。这个命令对象知道如何调用文件存储系统(接收者)来保存当前文档的内容。这种设计使得界面操作和实际的业务逻辑(文件存储、读取等)分离开来,便于维护和扩展。同时,一些高级的 GUI 应用还支持撤销和重做操作,这也是通过命令模式实现的。每次用户执行一个操作(如修改字体、插入图片),都会创建一个相应的命令对象,这些命令对象被存储在一个栈中,用于支持撤销和重做操作。
- 事务处理系统:在企业级的事务处理系统中,如银行的交易系统,每一笔交易(如存款、取款、转账)都可以看作是一个命令。客户通过 ATM 机或者网上银行(调用者)发起交易请求,这个请求被封装为一个交易命令(命令对象)。银行的核心交易处理系统(接收者)会根据这个命令执行相应的操作,如更新账户余额、记录交易日志等。命令模式使得银行可以很容易地对交易进行排队、记录和撤销,确保交易的准确性和安全性。
二、编程实现
下面是一个使用Java编写的命令模式示例程序,它演示了如何通过命令模式来实现一个简单的文本编辑器的功能,包括复制、粘贴和撤销操作。
2.1 定义命令接收者
// 接收者类 - 定义了具体的业务逻辑
class TextEditor {
private String clipboard = ""; // 用于保存剪贴板内容
public void copy(String text) {
System.out.println("Copying text: " + text);
this.clipboard = text;
}
public void paste() {
if (!clipboard.isEmpty()) {
System.out.println("Pasting text: " + clipboard);
} else {
System.out.println("Clipboard is empty, nothing to paste.");
}
}
public void clearClipboard() {
System.out.println("Clearing clipboard.");
this.clipboard = "";
}
}
2.2 定义命令接口及对象
// 抽象命令接口
interface Command {
void execute(); // 执行命令
void undo(); // 撤销命令
}
// 具体命令类 - 复制命令
class CopyCommand implements Command {
private TextEditor editor;
private String textToCopy;
public CopyCommand(TextEditor editor, String textToCopy) {
this.editor = editor;
this.textToCopy = textToCopy;
}
@Override
public void execute() {
editor.copy(textToCopy);
}
@Override
public void undo() {
editor.clearClipboard();
}
}
// 具体命令类 - 粘贴命令
class PasteCommand implements Command {
private TextEditor editor;
private String previousContent;
public PasteCommand(TextEditor editor) {
this.editor = editor;
this.previousContent = editor.clipboard; // 记录当前剪贴板内容以便撤销
}
@Override
public void execute() {
editor.paste();
}
@Override
public void undo() {
editor.copy(previousContent); // 恢复到执行前的状态
}
}
2.3 定义命令发出者
// 调用者类 - 负责触发命令的执行
class EditorInvoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
if (command != null) {
command.execute();
}
}
public void undo() {
if (command != null) {
command.undo();
}
}
}
2.4 测试程序
// 测试命令模式的应用
public class CommandPatternDemo {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
EditorInvoker invoker = new EditorInvoker();
// 设置并执行复制命令
invoker.setCommand(new CopyCommand(editor, "Hello World"));
invoker.pressButton();
// 设置并执行粘贴命令
invoker.setCommand(new PasteCommand(editor));
invoker.pressButton();
// 撤销上一步操作(即撤销粘贴)
invoker.undo();
// 再次尝试粘贴,应该显示剪贴板为空的信息
invoker.pressButton();
// 撤销上一步操作(即撤销复制)
invoker.undo();
}
}
输出结果
Copying text: Hello World
Pasting text: Hello World
Copying text: Hello World
Clipboard is empty, nothing to paste.
Clearing clipboard.
这段代码展示了如何利用命令模式将不同的编辑操作封装为独立的对象(命令)。每个命令对象都实现了Command接口,并且可以被调用者统一管理。通过这种方式,我们可以轻松地添加新的命令类型,而不会影响已有的代码结构。此外,命令模式还使得我们能够方便地实现撤销功能,只需让每个命令记住足够的信息来恢复其执行之前的状态即可。
文章同步更新在个人微信公众号:IT路旅记
感兴趣的伙伴可以关注订阅,方便浏览。