
Command を Java で
Command は、 振る舞いに関するデザインパターンの一つで、 リクエストや簡単な操作をオブジェクトに変換します。
変換により、 コマンドの遅延実行や遠隔実行を可能にしたり、 コマンドの履歴の保存を可能にしたりできます。
複雑度:
人気度:
使用例: Command パターンは、 Java コードではよく見かけます。 最もよく使われるのは、 UI 要素をアクションでパラメーター化する時のコールバックの代わりとしてです。 また、 タスクをキューに入れたり、 操作履歴の管理などでも使われます。
Java のコア・ライブラリーでの Command の使用例です:
-
java.lang.Runnable
の全実装 -
javax.swing.Action
の全実装
見つけ方: 特定の操作 (コピー、 切り取り、 ペースト、 送信、 印刷など) を表す、 関連したクラスの集まりを見つけたら、 Command パターンを使用しているかもしれません。 これらのクラスは、 同一のインターフェースか抽象クラスを実装しているはずです。 コマンドは、 関連する操作を独自に実装するかもしれませんし、 別個のオブジェクト ( 「受け手」) に作業を委任するかもしれません。 このパターンの識別に必要な最後の段階は、 インボーカー (送り手) を特定することです。 メソッドやコンストラクターのパラメーターにコマンド・オブジェクトを受け付けるようなクラスを探してみてください。
テキスト・エディターのコマンドと取り消し操作
この例のテキスト・エディターは、 ユーザーが何らかの操作をするたびに新しいコマンド・オブジェクトを作成します。 コマンドは、 そのアクションの実行後に、 履歴スタックにプッシュされます。
ここで、 取り消し操作を実行します。 アプリケーションは、 履歴から最後に実行されたコマンドを取り出し、 逆のことを行うアクションを実行するか、 そのコマンドによって保存されたエディターの過去の状態を復元します。
commands
commands/Command.java: コマンドの抽象基底クラス
package refactoring_guru.command.example.commands;
import refactoring_guru.command.example.editor.Editor;
public abstract class Command {
public Editor editor;
private String backup;
Command(Editor editor) {
this.editor = editor;
}
void backup() {
backup = editor.textField.getText();
}
public void undo() {
editor.textField.setText(backup);
}
public abstract boolean execute();
}
commands/CopyCommand.java: 選択されたテキストをクリップボードへコピー
package refactoring_guru.command.example.commands;
import refactoring_guru.command.example.editor.Editor;
public class CopyCommand extends Command {
public CopyCommand(Editor editor) {
super(editor);
}
@Override
public boolean execute() {
editor.clipboard = editor.textField.getSelectedText();
return false;
}
}
commands/PasteCommand.java: クリップボードからテキストをペースト
package refactoring_guru.command.example.commands;
import refactoring_guru.command.example.editor.Editor;
public class PasteCommand extends Command {
public PasteCommand(Editor editor) {
super(editor);
}
@Override
public boolean execute() {
if (editor.clipboard == null || editor.clipboard.isEmpty()) return false;
backup();
editor.textField.insert(editor.clipboard, editor.textField.getCaretPosition());
return true;
}
}
commands/CutCommand.java: クリップボードへテキストを切り取り
package refactoring_guru.command.example.commands;
import refactoring_guru.command.example.editor.Editor;
public class CutCommand extends Command {
public CutCommand(Editor editor) {
super(editor);
}
@Override
public boolean execute() {
if (editor.textField.getSelectedText().isEmpty()) return false;
backup();
String source = editor.textField.getText();
editor.clipboard = editor.textField.getSelectedText();
editor.textField.setText(cutString(source));
return true;
}
private String cutString(String source) {
String start = source.substring(0, editor.textField.getSelectionStart());
String end = source.substring(editor.textField.getSelectionEnd());
return start + end;
}
}
commands/CommandHistory.java: コマンド履歴
package refactoring_guru.command.example.commands;
import java.util.Stack;
public class CommandHistory {
private Stack<Command> history = new Stack<>();
public void push(Command c) {
history.push(c);
}
public Command pop() {
return history.pop();
}
public boolean isEmpty() { return history.isEmpty(); }
}
editor
editor/Editor.java: テキスト・エディターの GUI
package refactoring_guru.command.example.editor;
import refactoring_guru.command.example.commands.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Editor {
public JTextArea textField;
public String clipboard;
private CommandHistory history = new CommandHistory();
public void init() {
JFrame frame = new JFrame("Text editor (type & use buttons, Luke!)");
JPanel content = new JPanel();
frame.setContentPane(content);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
textField = new JTextArea();
textField.setLineWrap(true);
content.add(textField);
JPanel buttons = new JPanel(new FlowLayout(FlowLayout.CENTER));
JButton ctrlC = new JButton("Ctrl+C");
JButton ctrlX = new JButton("Ctrl+X");
JButton ctrlV = new JButton("Ctrl+V");
JButton ctrlZ = new JButton("Ctrl+Z");
Editor editor = this;
ctrlC.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
executeCommand(new CopyCommand(editor));
}
});
ctrlX.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
executeCommand(new CutCommand(editor));
}
});
ctrlV.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
executeCommand(new PasteCommand(editor));
}
});
ctrlZ.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
undo();
}
});
buttons.add(ctrlC);
buttons.add(ctrlX);
buttons.add(ctrlV);
buttons.add(ctrlZ);
content.add(buttons);
frame.setSize(450, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private void executeCommand(Command command) {
if (command.execute()) {
history.push(command);
}
}
private void undo() {
if (history.isEmpty()) return;
Command command = history.pop();
if (command != null) {
command.undo();
}
}
}
Demo.java: クライアント・コード
package refactoring_guru.command.example;
import refactoring_guru.command.example.editor.Editor;
public class Demo {
public static void main(String[] args) {
Editor editor = new Editor();
editor.init();
}
}
OutputDemo.png: 実行結果
