Command (Kommando)
Introduktion
Command är ett beteendemönster som omvandlar en begäran till ett självständigt objekt. Det låter dig skicka begäranden som argument, köa dem, logga dem och stödja ångra-operationer (undo).
Problem
Du bygger en textredigerare med knappar i verktygsfältet: Spara, Kopiera, Klistra in. Varje knapp gör något annorlunda, men de ser likadana ut.
Det enklaste vore att skapa underklasser för varje knapp — SaveButton, CopyButton. Men du skulle snart ha hundratals underklasser, och “Spara” anropas också från menyn och via kortkommando Ctrl+S. Samma logik på tre ställen.
Lösning
Command-mönstret extraherar begäran — vad som ska göras — till ett eget objekt med en execute()-metod. GUI-objektet (knappen) håller bara en referens till ett kommandoobjekt och anropar execute() när det klickas. Det spelar ingen roll om kommandot kom från en knapp, ett menyalternativ eller ett kortkommando.
När ska Command användas?
- När du vill parametrisera objekt med operationer.
- När du vill köa, schemalägga eller logga operationer.
- När du vill implementera ångra/gör om (undo/redo).
Struktur
| Roll | Ansvar |
|---|---|
| Command Interface | Deklarerar execute() (och ofta undo()). |
| Concrete Command | Implementerar operationen; håller en referens till Receiver. |
| Receiver | Klassen som faktiskt utför arbetet (t.ex. Editor). |
| Invoker | Triggar kommandot (t.ex. en knapp eller kortkommando). |
| Klient | Skapar och konfigurerar kommandoobjekten. |
Exempel — Textredigerare med Undo (Java)
Scenariot: En textredigerare där kopiera, klistra in och ångra implementeras via Command-mönstret.
import java.util.ArrayDeque;
import java.util.Deque;
// ── Receiver ──────────────────────────────────────────────
public class Editor {
private StringBuilder text = new StringBuilder();
private String clipboard = "";
public String getText() { return text.toString(); }
public void setText(String t) { text = new StringBuilder(t); }
public String getClipboard() { return clipboard; }
public void setClipboard(String c) { clipboard = c; }
public void deleteSelection(int start, int end) {
text.delete(start, end);
}
public void insertAt(int pos, String str) {
text.insert(pos, str);
}
}
// ── Command Interface ─────────────────────────────────────
public interface Command {
boolean execute();
void undo();
}
// ── Concrete Commands ─────────────────────────────────────
public class CopyCommand implements Command {
private Editor editor;
private int start, end;
public CopyCommand(Editor editor, int start, int end) {
this.editor = editor;
this.start = start;
this.end = end;
}
@Override
public boolean execute() {
editor.setClipboard(editor.getText().substring(start, end));
return false; // Kopiering ändrar inte texten — inget att ångra
}
@Override public void undo() { /* Ingenting att ångra */ }
}
public class PasteCommand implements Command {
private Editor editor;
private int insertPos;
private String backup = "";
public PasteCommand(Editor editor, int insertPos) {
this.editor = editor;
this.insertPos = insertPos;
}
@Override
public boolean execute() {
backup = editor.getText();
editor.insertAt(insertPos, editor.getClipboard());
return true;
}
@Override
public void undo() {
editor.setText(backup);
}
}
// ── Invoker ───────────────────────────────────────────────
public class CommandHistory {
private Deque<Command> history = new ArrayDeque<>();
public void push(Command c) { history.push(c); }
public void undo() {
if (!history.isEmpty()) {
history.pop().undo();
}
}
}
// ── Klientkod ─────────────────────────────────────────────
public class Application {
public static void main(String[] args) {
Editor editor = new Editor();
CommandHistory history = new CommandHistory();
editor.setText("Hej världen!");
System.out.println("Start: " + editor.getText());
// Kopiera tecken 4–11 ("världen")
Command copy = new CopyCommand(editor, 4, 11);
copy.execute();
System.out.println("Urklipp: " + editor.getClipboard());
// Klistra in i slutet
Command paste = new PasteCommand(editor, editor.getText().length());
if (paste.execute()) history.push(paste);
System.out.println("Efter klistra: " + editor.getText());
// Ångra
history.undo();
System.out.println("Efter undo: " + editor.getText());
}
}
Output:
Start: Hej världen!
Urklipp: världen
Efter klistra: Hej världen!världen
Efter undo: Hej världen!
Fördelar
- Single Responsibility Principle — Frikopplar klassen som triggar en operation från klassen som utför den.
- Open/Closed Principle — Lägg till nya kommandon utan att ändra befintlig kod.
- Ångra/gör om (undo/redo) är enkelt att implementera.
- Kommandon kan köas, loggas och schemaläggas.
Nackdelar
- Koden kan bli mer komplex — du introducerar ett extra lager av klasser.
Av Victor Hernandez från Bytebase.se