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.
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 (C# / .NET)
Scenariot: En textredigerare där kopiera, klistra in och ångra implementeras via Command-mönstret.
using System.Collections.Generic;
// ── Receiver ──────────────────────────────────────────────
public class Editor
{
public string Text { get; set; } = string.Empty;
public string Clipboard { get; private set; } = string.Empty;
public void Copy(int start, int length) =>
Clipboard = Text.Substring(start, length);
public void Paste(int pos) =>
Text = Text.Insert(pos, Clipboard);
}
// ── Command Interface ─────────────────────────────────────
public interface ICommand
{
bool Execute();
void Undo();
}
// ── Concrete Commands ─────────────────────────────────────
public class CopyCommand(Editor editor, int start, int length) : ICommand
{
public bool Execute()
{
editor.Copy(start, length);
return false; // Kopiering ändrar inte texten
}
public void Undo() { /* Ingenting att ångra */ }
}
public class PasteCommand(Editor editor, int insertPos) : ICommand
{
private string _backup = string.Empty;
public bool Execute()
{
_backup = editor.Text;
editor.Paste(insertPos);
return true;
}
public void Undo() => editor.Text = _backup;
}
// ── Invoker ───────────────────────────────────────────────
public class CommandHistory
{
private readonly Stack<ICommand> _history = new();
public void Push(ICommand command) => _history.Push(command);
public void Undo()
{
if (_history.TryPop(out ICommand? command))
command.Undo();
}
}
// ── Klientkod ─────────────────────────────────────────────
Editor editor = new();
CommandHistory history = new();
editor.Text = "Hej världen!";
Console.WriteLine($"Start: {editor.Text}");
// Kopiera "världen" (7 tecken från pos 4)
CopyCommand copy = new(editor, 4, 7);
copy.Execute();
Console.WriteLine($"Urklipp: {editor.Clipboard}");
// Klistra in i slutet
PasteCommand paste = new(editor, editor.Text.Length);
if (paste.Execute()) history.Push(paste);
Console.WriteLine($"Efter klistra: {editor.Text}");
// Ångra
history.Undo();
Console.WriteLine($"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