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

RollAnsvar
Command InterfaceDeklarerar execute() (och ofta undo()).
Concrete CommandImplementerar operationen; håller en referens till Receiver.
ReceiverKlassen som faktiskt utför arbetet (t.ex. Editor).
InvokerTriggar kommandot (t.ex. en knapp eller kortkommando).
KlientSkapar 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