Command (Parancs)
Kategória: Viselkedési minta (Behavioral Pattern)
Lényege és célja
A Command minta egy kérést (műveletet) önálló objektummá alakít. Ez az objektum mindent magában hordoz, ami a művelet végrehajtásához kell: a címzettet (akit hív), a paramétereket, és gyakran a visszacsinálás (undo) logikáját is.
A művelet ezzel ugyanolyan értékké válik, mint bármi más: el lehet tárolni, sorba lehet rakni, naplózni, hálózaton átküldeni, vagy egy stack-en visszafelé lejátszani.
Miért zseniális ez a minta?
Egy szövegszerkesztős példán könnyű megérteni:
- Undo/Redo ingyen jár: ha minden művelet (
AppendCommand,DeleteCommand) tudja önmagát visszacsinálni, aCommandHistorycsak egyStack-en tárolja őket, és visszafelé hívja azundo()-t. - A hívó és a végrehajtó szétválik: a
CommandHistory(Invoker) nem tudja, mit csinál egy konkrét parancs – csakexecute()-t hív. Ez ideális makrók, ütemezett feladatok, sorba állítás vagy event sourcing esetén. - Új művelet bevezetése = új osztály: nem kell hozzányúlni a
TextEditor-hoz vagy aHistory-hoz, csak írunk egy újCommandimplementációt (Open/Closed).
Mikor használjuk?
- Amikor undo/redo, makró-rögzítés, vagy műveletnaplózás (audit log) szükséges.
- Amikor műveleteket sorba kell tenni, ütemezni, vagy másik szálon/szerveren végrehajtani (job queue, task scheduler).
- Amikor a felhasználói felület gombjait (GUI), menüpontjait, billentyűparancsait egységesen, paraméteres objektumként akarjuk a háttérlogikához kötni.
Mermaid Diagram
A diagramon a négy klasszikus szereplő látható: Command interfész, konkrét parancsok (AppendCommand, DeleteCommand), a Receiver (TextEditor) aki a tényleges munkát végzi, és az Invoker (CommandHistory) ami a parancsokat tárolja és lejátssza.
classDiagram
class Command {
<<interface>>
+execute() void
+undo() void
}
class AppendCommand {
-textEditor: TextEditor
-text: String
+execute() void
+undo() void
}
class DeleteCommand {
-textEditor: TextEditor
-count: int
-text: String
+execute() void
+undo() void
}
class TextEditor {
-content: StringBuilder
+append(text: String) void
+deleteLast(count: int) String
}
class CommandHistory {
-commands: Stack~Command~
+executeCommand(c: Command) void
+undo() void
}
class Client
AppendCommand ..|> Command : "implementálja"
DeleteCommand ..|> Command : "implementálja"
%% A konkrét parancsok ismerik a Receivert (TextEditor)
AppendCommand --> TextEditor : "delegál"
DeleteCommand --> TextEditor : "delegál"
%% Invoker csak a Command interfészt ismeri
CommandHistory o--> Command : "tárolja"
Client --> CommandHistory : "használja"
Client --> TextEditor : "példányosítja" Forráskód
package behavioral.command;
import java.util.Objects;
import java.util.Stack;
interface Command {
void execute();
void undo();
}
class TextEditor {
private final StringBuilder content = new StringBuilder();
void append(String text) {
content.append(text);
}
String deleteLast(int count) {
var text = content.toString();
content.delete(content.length() - count, content.length());
return text.substring(text.length() - count);
}
String getText() {
return content.toString();
}
}
class AppendCommand implements Command {
private final TextEditor textEditor;
private final String text;
public AppendCommand(TextEditor textEditor, String text) {
this.textEditor = textEditor;
this.text = text;
}
@Override
public void execute() {
textEditor.append(text);
}
@Override
public void undo() {
textEditor.deleteLast(text.length());
}
}
class DeleteCommand implements Command {
private final TextEditor textEditor;
private String text;
private final int count;
public DeleteCommand(TextEditor textEditor, int count) {
this.textEditor = textEditor;
this.count = count;
}
@Override
public void execute() {
text = textEditor.deleteLast(count);
}
@Override
public void undo() {
if (Objects.nonNull(text)) {
textEditor.append(text);
}
}
}
class CommandHistory {
private final Stack<Command> commands = new Stack<>();
void executeCommand(Command command) {
command.execute();
commands.add(command);
}
void undo() {
if(!commands.isEmpty()) {
commands.pop().undo();
}
}
}
class Main {
static void main() {
final var textEditor = new TextEditor();
final var history = new CommandHistory();
history.executeCommand(new AppendCommand(textEditor, "Hello "));
history.executeCommand(new AppendCommand(textEditor, "World!"));
history.executeCommand(new DeleteCommand(textEditor, 6));
history.undo();
history.undo();
history.executeCommand(new AppendCommand(textEditor, "World!"));
System.out.println(textEditor.getText());
}
}