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

RollAnsvar
Command InterfaceDeklarerar execute() och undo().
Concrete CommandImplementerar operationen; håller en referens till Receiver.
ReceiverKlassen som faktiskt utför arbetet.
InvokerTriggar kommandot.
KlientSkapar och konfigurerar kommandoobjekten.

Exempel — Textredigerare med Undo (PHP)

Scenariot: En textredigerare där kopiera, klistra in och ångra implementeras via Command-mönstret.

<?php

// ── Receiver ──────────────────────────────────────────────

class Editor
{
    private string $text      = '';
    private string $clipboard = '';

    public function getText(): string      { return $this->text; }
    public function setText(string $t): void { $this->text = $t; }
    public function getClipboard(): string { return $this->clipboard; }

    public function copy(int $start, int $end): void
    {
        $this->clipboard = substr($this->text, $start, $end - $start);
    }

    public function paste(int $pos): void
    {
        $this->text = substr($this->text, 0, $pos)
            . $this->clipboard
            . substr($this->text, $pos);
    }
}

// ── Command Interface ─────────────────────────────────────

interface Command
{
    public function execute(): bool;
    public function undo(): void;
}

// ── Concrete Commands ─────────────────────────────────────

class CopyCommand implements Command
{
    public function __construct(
        private Editor $editor,
        private int    $start,
        private int    $end
    ) {}

    public function execute(): bool
    {
        $this->editor->copy($this->start, $this->end);
        return false; // Kopiering ändrar inte texten
    }

    public function undo(): void { /* Ingenting att ångra */ }
}

class PasteCommand implements Command
{
    private string $backup = '';

    public function __construct(
        private Editor $editor,
        private int    $insertPos
    ) {}

    public function execute(): bool
    {
        $this->backup = $this->editor->getText();
        $this->editor->paste($this->insertPos);
        return true;
    }

    public function undo(): void
    {
        $this->editor->setText($this->backup);
    }
}

// ── Invoker ───────────────────────────────────────────────

class CommandHistory
{
    private array $history = [];

    public function push(Command $command): void
    {
        $this->history[] = $command;
    }

    public function undo(): void
    {
        $command = array_pop($this->history);
        $command?->undo();
    }
}

// ── Klientkod ─────────────────────────────────────────────

$editor  = new Editor();
$history = new CommandHistory();

$editor->setText('Hej världen!');
echo 'Start:         ' . $editor->getText() . "\n";

// Kopiera "världen" (tecken 4-11)
$copy = new CopyCommand($editor, 4, 11);
$copy->execute();
echo 'Urklipp:       ' . $editor->getClipboard() . "\n";

// Klistra in i slutet
$paste = new PasteCommand($editor, strlen($editor->getText()));
if ($paste->execute()) $history->push($paste);
echo 'Efter klistra: ' . $editor->getText() . "\n";

// Ångra
$history->undo();
echo 'Efter undo:    ' . $editor->getText() . "\n";

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