Menu
Coddy logo textTech

Befehlsmuster

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 96 von 104.

Das Command-Muster kapselt eine Anfrage als Objekt und ermöglicht es dir, Clients mit unterschiedlichen Anfragen zu parametrisieren, Operationen in eine Warteschlange einzureihen oder eine Rückgängig-Funktion zu unterstützen. Es entkoppelt das Objekt, das eine Operation aufruft, von demjenigen, das weiß, wie sie ausgeführt wird.

Das Muster hat vier zentrale Komponenten: eine Command-Schnittstelle mit einer execute()-Methode, konkrete Befehle, die bestimmte Aktionen implementieren, einen Empfänger, der die eigentliche Arbeit ausführt, und einen Aufrufer, der Befehle auslöst:

#include <iostream>
#include <memory>
#include <vector>

// Empfänger - weiß, wie Operationen auszuführen sind
class Light {
public:
    void turnOn() { std::cout << "Light is ON\n"; }
    void turnOff() { std::cout << "Light is OFF\n"; }
};

// Befehlsschnittstelle
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};

// Konkrete Befehle
class LightOnCommand : public Command {
    Light& light;
public:
    LightOnCommand(Light& l) : light(l) {}
    void execute() override { light.turnOn(); }
    void undo() override { light.turnOff(); }
};

class LightOffCommand : public Command {
    Light& light;
public:
    LightOffCommand(Light& l) : light(l) {}
    void execute() override { light.turnOff(); }
    void undo() override { light.turnOn(); }
};

// Aufrufer
class RemoteControl {
    std::vector<std::unique_ptr<Command>> history;
public:
    void pressButton(std::unique_ptr<Command> cmd) {
        cmd->execute();
        history.push_back(std::move(cmd));
    }
    
    void pressUndo() {
        if (!history.empty()) {
            history.back()->undo();
            history.pop_back();
        }
    }
};

Jeder Befehl speichert eine Referenz auf seinen Empfänger und ruft bei der Ausführung die entsprechende Methode auf. Der Aufrufer weiß nicht, welche Aktion ausgeführt wird – er ruft einfach execute() auf. Indem du Befehle in einem Verlauf speicherst, kannst du Rückgängig problemlos implementieren, indem du die undo()-Methode jedes Befehls aufrufst.

Verwende Command, wenn du Vorgänge in die Warteschlange stellen, Rückgängig/Wiederholen implementieren oder den Sender einer Anfrage von ihrem Handler entkoppeln musst.

challenge icon

Aufgabe

Einfach

Erstellen wir einen Texteditor mit Rückgängig-Funktionalität unter Verwendung des Befehlsmusters. Du erstellst ein System, in dem Bearbeitungsvorgänge (wie das Einfügen und Löschen von Text) als Befehlsobjekte gekapselt werden, sodass Benutzer Aktionen ausführen und sie anschließend in umgekehrter Reihenfolge rückgängig machen können – genau wie in einem echten Texteditor.

Du organisierst deinen Code auf vier Dateien verteilt:

  • TextDocument.h: Erstelle die Empfängerklasse, die den eigentlichen Textinhalt enthält und bearbeitet.

    Deine Klasse TextDocument sollte den Inhalt des Dokuments als Zeichenkette speichern. Implementiere diese Methoden:

    • insertText(const std::string& text): fügt Text am Ende des Inhalts an
    • deleteText(int count): entfernt die letzten count Zeichen aus dem Inhalt (tut nichts, wenn count die Inhaltslänge überschreitet)
    • getContent(): gibt den aktuellen Inhalt als konstante Referenz zurück
  • Command.h: Definiere die Befehlsschnittstelle und konkrete Befehlsklassen.

    Erstelle eine abstrakte Klasse Command mit den rein virtuellen Methoden execute() und undo() sowie einem virtuellen Destruktor.

    Implementiere anschließend zwei konkrete Befehle:

    • InsertCommand: übernimmt eine Referenz auf ein TextDocument und eine einzufügende Zeichenkette. Bei der Ausführung fügt es den Text ein. Beim Rückgängigmachen löscht es dieselbe Anzahl an Zeichen, die eingefügt wurde.
    • DeleteCommand: übernimmt eine Referenz auf ein TextDocument und die Anzahl der zu löschenden Zeichen. Es muss den gelöschten Text speichern, damit es ihn beim Rückgängigmachen wiederherstellen kann. Bei der Ausführung entfernt es Zeichen vom Ende. Beim Rückgängigmachen fügt es den gespeicherten Text wieder ein.
  • TextEditor.h: Erstelle den Aufrufer, der die Befehlsausführung und den Verlauf verwaltet.

    Deine Klasse TextEditor sollte eine Referenz auf ein TextDocument enthalten und einen Verlauf der ausgeführten Befehle mithilfe eines Vektors von unique_ptr<Command> verwalten. Implementiere:

    • executeCommand(std::unique_ptr<Command> cmd): führt den Befehl aus und fügt ihn dem Verlauf hinzu
    • undo(): macht den zuletzt ausgeführten Befehl rückgängig und entfernt ihn aus dem Verlauf (tut nichts, wenn der Verlauf leer ist)
    • showContent(): gibt den aktuellen Dokumentinhalt aus oder [empty], wenn der Inhalt leer ist
  • main.cpp: Veranschauliche das Befehlsmuster mit Rückgängig-Funktionalität.

    Lies drei Eingaben ein:

    1. Erster einzufügender Text (Zeichenkette)
    2. Zweiter einzufügender Text (Zeichenkette)
    3. Anzahl der zu löschenden Zeichen (Ganzzahl)

    Erstelle ein TextDocument und einen TextEditor. Führe anschließend diese Vorgänge in der angegebenen Reihenfolge aus und zeige nach jedem Schritt den Inhalt an:

    1. Füge den ersten Text ein und zeige anschließend den Inhalt an
    2. Füge den zweiten Text ein und zeige anschließend den Inhalt an
    3. Lösche die angegebene Anzahl an Zeichen und zeige anschließend den Inhalt an
    4. Mache dies einmal rückgängig und zeige anschließend den Inhalt an
    5. Mache dies ein weiteres Mal rückgängig und zeige anschließend den Inhalt an

Beispielsweise mit den Eingaben Hello, World und 3:

Hello
Hello World
Hello Wo
Hello World
Hello

Mit den Eingaben Code, Editor und 6:

Code
CodeEditor
Code
CodeEditor
Code

Beachte, wie jeder Befehl weiß, wie er sich selbst umkehren kann. Der DeleteCommand merkt sich, was er gelöscht hat, damit er es wiederherstellen kann, und der InsertCommand weiß genau, wie viele Zeichen beim Rückgängigmachen entfernt werden müssen. Das ist die Stärke des Befehlsmusters: Vorgänge werden zu Objekten erster Klasse, die gespeichert, ausgeführt und umgekehrt werden können.

Probier es selbst

#include <iostream>
#include <string>
#include <memory>
#include "TextDocument.h"
#include "Command.h"
#include "TextEditor.h"

int main() {
    // Eingaben lesen
    std::string firstText;
    std::string secondText;
    int deleteCount;
    
    std::getline(std::cin, firstText);
    std::getline(std::cin, secondText);
    std::cin >> deleteCount;

    // TODO: Erstelle ein TextDocument und einen TextEditor
    
    // TODO: Führe die Operationen der Reihe nach aus und zeige nach jeder den Inhalt:
    // 1. Füge den ersten Text ein, dann zeige den Inhalt
    // 2. Füge den zweiten Text ein, dann zeige den Inhalt
    // 3. Lösche die angegebene Anzahl von Zeichen, dann zeige den Inhalt
    // 4. Mache einmal rückgängig, dann zeige den Inhalt
    // 5. Mache noch einmal rückgängig, dann zeige den Inhalt
    
    // Hinweis: Verwende std::make_unique<InsertCommand>(...) und 
    //       std::make_unique<DeleteCommand>(...) um Befehle zu erstellen

    return 0;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-C++-Compiler