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. Men du skulle snart ha hundratals underklasser, och “Spara” anropas också från menyn och via Ctrl+S. Samma logik på tre ställen.
Lösning
Command-mönstret extraherar begäran till ett eget objekt med en execute()-metod. GUI-objektet håller bara en referens till ett kommandoobjekt och anropar execute() vid klick — oavsett om det 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 undo(). |
| Concrete Command | Implementerar operationen; håller en referens till Receiver. |
| Receiver | Klassen som faktiskt utför arbetet. |
| Invoker | Triggar kommandot. |
| Klient | Skapar och konfigurerar kommandoobjekten. |
Exempel — Textredigerare med Undo (TypeScript)
Scenariot: En textredigerare där kopiera, klistra in och ångra implementeras via Command-mönstret.
// ── Receiver ──────────────────────────────────────────────
class Editor {
private _text = '';
private _clipboard = '';
get text() { return this._text; }
set text(v) { this._text = v; }
get clipboard() { return this._clipboard; }
copy(start: number, end: number): void {
this._clipboard = this._text.slice(start, end);
}
paste(pos: number): void {
this._text = this._text.slice(0, pos) + this._clipboard + this._text.slice(pos);
}
}
// ── Command Interface ─────────────────────────────────────
interface Command {
execute(): boolean;
undo(): void;
}
// ── Concrete Commands ─────────────────────────────────────
class CopyCommand implements Command {
constructor(private editor: Editor, private start: number, private end: number) {}
execute(): boolean {
this.editor.copy(this.start, this.end);
return false; // Kopiering ändrar inte texten
}
undo(): void { /* Ingenting att ångra */ }
}
class PasteCommand implements Command {
private backup = '';
constructor(private editor: Editor, private insertPos: number) {}
execute(): boolean {
this.backup = this.editor.text;
this.editor.paste(this.insertPos);
return true;
}
undo(): void {
this.editor.text = this.backup;
}
}
// ── Invoker ───────────────────────────────────────────────
class CommandHistory {
private history: Command[] = [];
push(command: Command): void { this.history.push(command); }
undo(): void {
const command = this.history.pop();
command?.undo();
}
}
// ── Klientkod ─────────────────────────────────────────────
const editor = new Editor();
const history = new CommandHistory();
editor.text = 'Hej världen!';
console.log('Start: ', editor.text);
// Kopiera "världen" (tecken 4-11)
const copy = new CopyCommand(editor, 4, 11);
copy.execute();
console.log('Urklipp: ', editor.clipboard);
// Klistra in i slutet
const paste = new PasteCommand(editor, editor.text.length);
if (paste.execute()) history.push(paste);
console.log('Efter klistra: ', editor.text);
// Ångra
history.undo();
console.log('Efter undo: ', editor.text);
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 triggar från utförare.
- Open/Closed Principle — Lägg till nya kommandon utan att ändra befintlig kod.
- Ångra/gör om (undo/redo) är enkelt att implementera.
Nackdelar
- Koden kan bli mer komplex med ett extra lager av klasser.
Av Victor Hernandez från Bytebase.se