命令模式(Command Pattern)是一种行为型设计模式,它将请求封装为一个对象,从而使得请求的发送者和接收者解耦。
一、命令模式的核心思想
命令模式的核心思想是将“请求”封装成为一个对象,从而使得我们可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行相应的操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。
二、命令模式的结构
在命令模式结构图中,通常包含以下几个角色:
三、命令模式的实现
命令模式的实现通常涉及以下几个步骤:
四、命令模式的应用场景
命令模式有广泛的应用场景,主要包括以下几种:
五、命令模式的优点
六、命令模式的缺点
七、实战流程
不使用命令模式
public class Editor {
public void copy() {
// 复制逻辑
}
public void paste() {
// 粘贴逻辑
}
public void undo() {
// 撤销逻辑
}
}
public class User {
public static void main(String[] args) {
Editor editor = new Editor();
editor.copy();
editor.paste();
editor.undo();
}
}
使用命令模式优化
/**
* 接收者
*/
public class Editor {
public void copy() {
System.out.println("复制内容");
}
public void paste() {
System.out.println("粘贴内容");
}
public void undo() {
System.out.println("撤销操作");
}
}
/**
* 命令接口
*/
public interface Command {
void execute();
}
/**
* 具体命令类
*/
public class CopyCommand implements Command {
private Editor editor;
public CopyCommand(Editor editor) {
this.editor = editor;
}
@Override
public void execute() {
editor.copy();
}
}
public class PasteCommand implements Command {
private Editor editor;
public PasteCommand(Editor editor) {
this.editor = editor;
}
@Override
public void execute() {
editor.paste();
}
}
public class UndoCommand implements Command {
private Editor editor;
public UndoCommand(Editor editor) {
this.editor = editor;
}
@Override
public void execute() {
editor.undo();
}
}
/**
* 调用者
*/
public class Menu {
private Command copyCommand;
private Command pasteCommand;
private Command undoCommand;
public Menu(Command copyCommand, Command pasteCommand, Command undoCommand) {
this.copyCommand = copyCommand;
this.pasteCommand = pasteCommand;
this.undoCommand = undoCommand;
}
public void clickCopy() {
copyCommand.execute();
}
public void clickPaste() {
pasteCommand.execute();
}
public void clickUndo() {
undoCommand.execute();
}
}
public class CommandTest {
public static void main(String[] args) {
Editor editor = new Editor();
Command copy = new CopyCommand(editor);
Command paste = new PasteCommand(editor);
Command undo = new UndoCommand(editor);
Menu menu = new Menu(copy, paste, undo);
menu.clickCopy();
menu.clickPaste();
menu.clickUndo();
}
}
八、在JDK源码中应用命令模式的例子
1、java.lang.Runnable 和 java.util.concurrent.Callable
Runnable 和 Callable 接口是命令模式的典型例子。这两个接口封装了要在另一个线程中执行的任务,具体任务内容由 run() 方法或 call() 方法来实现,线程池或者线程的调用者在需要的时候执行这些任务。
(1)命令模式结构
Runnable 的 run() 方法是命令的执行逻辑,而 Thread 是调用者,它负责在合适的时间触发 run() 的执行。
(2)代码示例
public class CommandExample {
public static void main(String[] args) {
// 创建一个命令(Runnable任务)
Runnable command = new Runnable() {
@Override
public void run() {
System.out.println("执行命令: 线程中的任务");
}
};
// 调用者:Thread 执行命令
Thread thread = new Thread(command);
thread.start();
}
}
2、javax.swing.Action
在 Swing 框架中,Action 接口用于封装行为,它是典型的命令模式。Action 可以被 JButton、JMenuItem 等组件复用,从而将动作的定义与其使用解耦。
(1)命令模式结构
(2)代码示例
sayHelloAction 是命令,JButton 是调用者,点击按钮会触发 actionPerformed 方法的执行。
import javax.swing.*;
import java.awt.event.ActionEvent;
public class SwingCommandExample {
public static void main(String[] args) {
// 创建一个具体命令(Action)
Action sayHelloAction = new AbstractAction("Say Hello") {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Hello, World!");
}
};
// 调用者:JButton,触发命令的执行
JButton button = new JButton(sayHelloAction);
// 创建一个简单的Swing窗口
JFrame frame = new JFrame("Command Pattern Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(button);
frame.pack();
frame.setVisible(true);
}
}
3、java.util.Timer 和 java.util.TimerTask
TimerTask 是 JDK 中另一个典型的命令模式实现。TimerTask 类实现了 Runnable 接口,其 run() 方法封装了定时要执行的任务,Timer 类负责调度这些任务的执行。
(1)命令模式结构
(2)代码示例
TimerTask 的 run() 方法是封装的任务逻辑,Timer 是负责触发任务执行的调用者。
import java.util.Timer;
import java.util.TimerTask;
public class TimerCommandExample {
public static void main(String[] args) {
// 创建一个定时器
Timer timer = new Timer();
// 创建一个具体命令(TimerTask)
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("执行定时任务");
}
};
// 调用者:Timer,调度任务
timer.schedule(task, 1000); // 1秒后执行任务
}
}