Command Pattern

Command Pattern

vg

Das Command Pattern ist ein Verhaltensmuster, das eine Anfrage als Objekt kapselt. Dies ermöglicht, Parameter für die Anfrage zu speichern, und ermöglicht, die Anfrage später auszuführen oder zu verwalten. Es entkoppelt den Sender einer Anfrage von dem Empfänger, der sie bearbeitet. Dieses Muster fördert eine klare Trennung zwischen den Aufrufern von Befehlen und deren Ausführungen.

Was ist das Command Pattern?

Im Command Pattern wird eine Anfrage in ein Objekt umgewandelt, das den Befehl und die dazugehörigen Parameter enthält. Der Befehl kann später ausgeführt werden, ohne dass der Sender wissen muss, wie der Befehl tatsächlich ausgeführt wird. Auf diese Weise wird der Sender vom Empfänger des Befehls entkoppelt. Das Command Pattern eignet sich besonders, wenn das Verhalten zur Laufzeit geändert oder Rückgängig gemacht werden muss.

Komponenten des Command Patterns

Das Command Pattern besteht aus mehreren Schlüsselaspekten:

  1. Command (Befehl): Eine abstrakte Klasse oder Schnittstelle, die eine Methode zur Ausführung des Befehls definiert.
  2. ConcreteCommand (Konkreter Befehl): Eine Implementierung der Command-Schnittstelle, die eine spezifische Anfrage repräsentiert und den Empfänger aufruft, um die Anfrage auszuführen.
  3. Invoker (Aufrufer): Der Aufrufer ist derjenige, der den Befehl anfordert und ausführt. Er weiß nicht, wie der Befehl implementiert ist, sondern ruft nur die Execute-Methode auf.
  4. Receiver (Empfänger): Der Empfänger ist die Klasse, die die tatsächliche Arbeit ausführt. Er wird durch den Befehl aufgerufen, um eine Aktion durchzuführen.
  5. Client: Der Client erstellt die konkreten Befehle und konfiguriert den Invoker mit den entsprechenden Befehlen.

Beispiel des Command Patterns in C++

Angenommen, wir entwickeln eine Anwendung zur Steuerung von Geräten wie Lampen und Fernsehern. Der Benutzer möchte die Geräte ein- und ausschalten. Wir können das Command Pattern verwenden, um die Steuerbefehle zu kapseln und flexibel auszuführen.

Hier ein einfaches C++-Beispiel:

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

// Receiver: Der Empfänger, der die tatsächliche Arbeit verrichtet
class Light {
public:
    void turnOn() {
        std::cout << "Licht eingeschaltet\n";
    }
    void turnOff() {
        std::cout << "Licht ausgeschaltet\n";
    }
};

class TV {
public:
    void turnOn() {
        std::cout << "TV eingeschaltet\n";
    }
    void turnOff() {
        std::cout << "TV ausgeschaltet\n";
    }
};

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

// ConcreteCommand: Konkreter Befehl für Licht
class LightOnCommand : public Command {
private:
    Light* light;
public:
    LightOnCommand(Light* light) : light(light) {}
    void execute() override {
        light->turnOn();
    }
};

// ConcreteCommand: Konkreter Befehl für Licht ausschalten
class LightOffCommand : public Command {
private:
    Light* light;
public:
    LightOffCommand(Light* light) : light(light) {}
    void execute() override {
        light->turnOff();
    }
};

// ConcreteCommand: Konkreter Befehl für TV
class TVOnCommand : public Command {
private:
    TV* tv;
public:
    TVOnCommand(TV* tv) : tv(tv) {}
    void execute() override {
        tv->turnOn();
    }
};

// ConcreteCommand: Konkreter Befehl für TV ausschalten
class TVOffCommand : public Command {
private:
    TV* tv;
public:
    TVOffCommand(TV* tv) : tv(tv) {}
    void execute() override {
        tv->turnOff();
    }
};

// Invoker: Der Aufrufer, der den Befehl speichert und ausführt
class RemoteControl {
private:
    std::shared_ptr<Command> onCommand;
    std::shared_ptr<Command> offCommand;
public:
    void setCommand(std::shared_ptr<Command> on, std::shared_ptr<Command> off) {
        onCommand = on;
        offCommand = off;
    }

    void pressOnButton() {
        if (onCommand) {
            onCommand->execute();
        }
    }

    void pressOffButton() {
        if (offCommand) {
            offCommand->execute();
        }
    }
};

// Client
int main() {
    Light light;
    TV tv;

    // Erstelle konkrete Befehle
    std::shared_ptr<Command> lightOn = std::make_shared<LightOnCommand>(&light);
    std::shared_ptr<Command> lightOff = std::make_shared<LightOffCommand>(&light);
    std::shared_ptr<Command> tvOn = std::make_shared<TVOnCommand>(&tv);
    std::shared_ptr<Command> tvOff = std::make_shared<TVOffCommand>(&tv);

    // Erstelle RemoteControl und weise Befehle zu
    RemoteControl remote;
    remote.setCommand(lightOn, lightOff);
    remote.pressOnButton();
    remote.pressOffButton();

    remote.setCommand(tvOn, tvOff);
    remote.pressOnButton();
    remote.pressOffButton();

    return 0;
}

Erklärung des C++-Beispiels

  1. Receiver (Empfänger): In diesem Beispiel haben wir die Klassen Light und TV als Empfänger. Diese Klassen implementieren Methoden zum Ein- und Ausschalten der Geräte.
  2. Command (Befehl): Die abstrakte Command-Schnittstelle definiert die execute()-Methode. Jede konkrete Befehlsklasse implementiert diese Methode, um die spezifische Aktion (z. B. das Einschalten von Licht oder TV) auszuführen.
  3. ConcreteCommand (Konkrete Befehle): Die Klassen LightOnCommand, LightOffCommand, TVOnCommand und TVOffCommand sind konkrete Implementierungen des Command-Interfaces. Sie kapseln die Anfrage und leiten sie an die entsprechenden Empfänger weiter.
  4. Invoker (Aufrufer): Die Klasse RemoteControl speichert die Befehle und führt sie aus. Sie hat zwei Tasten: eine für das Einschalten und eine für das Ausschalten von Geräten. Der Benutzer kann die Schaltflächen drücken, um die Aktionen auszuführen.
  5. Client: Der Client erstellt die Empfänger, Befehle und den Aufrufer. Er verbindet alles, sodass der Aufrufer mit den entsprechenden Befehlen ausgestattet ist.

Beispiel des Command Patterns in Python

Das Command Pattern ist ein Verhaltensmuster, bei dem ein Objekt verwendet wird, um eine Anforderung (oder einen Befehl) an ein anderes Objekt zu übermitteln. Dabei wird die Anfrage als eigenständiges Objekt gekapselt, wodurch der Absender und der Empfänger voneinander entkoppelt werden.

Hier ist ein einfaches Beispiel des Command Patterns in Python:

Command Interface: Definiert die Schnittstelle für alle konkreten Befehle.

  1. Concrete Commands: Implementiert den Befehl und ruft die entsprechenden Methoden auf.
  2. Receiver: Das Objekt, das die eigentliche Arbeit erledigt (z.B. der Fernseher).
  3. Invoker: Verwaltet und führt die Befehle aus.
  4. Client: Erzeugt die Befehle und ordnet sie dem Invoker zu.
# Receiver - Das Objekt, das die eigentliche Arbeit erledigt
class Television:
    def on(self):
        print("Fernseher eingeschaltet")

    def off(self):
        print("Fernseher ausgeschaltet")

# Command Interface
class Command:
    def execute(self):
        pass

# Concrete Command für Einschalten
class TurnOnCommand(Command):
    def __init__(self, television):
        self.television = television

    def execute(self):
        self.television.on()

# Concrete Command für Ausschalten
class TurnOffCommand(Command):
    def __init__(self, television):
        self.television = television

    def execute(self):
        self.television.off()

# Invoker - Die Klasse, die den Befehl ausführt
class RemoteControl:
    def __init__(self):
        self.command = None

    def set_command(self, command):
        self.command = command

    def press_button(self):
        self.command.execute()

# Client - Der Code, der die Befehle zusammenstellt
def client_code():
    television = Television()  # Der Receiver

    turn_on = TurnOnCommand(television)  # Befehl zum Einschalten
    turn_off = TurnOffCommand(television)  # Befehl zum Ausschalten

    remote = RemoteControl()  # Der Invoker

    # Fernseher einschalten
    remote.set_command(turn_on)
    remote.press_button()

    # Fernseher ausschalten
    remote.set_command(turn_off)
    remote.press_button()

# Ausführen des Client-Codes
client_code()

Erklärung des Python Beispiels

  • Television ist der Receiver, der die tatsächlichen Operationen durchführt (einschalten und ausschalten).
  • Command ist die abstrakte Basisklasse, die eine execute-Methode definiert, die in den konkreten Befehlsklassen (TurnOnCommand, TurnOffCommand) überschrieben wird.
  • RemoteControl ist der Invoker, der den Befehl verwaltet und ausführt.
  • client_code() stellt den Befehl zusammen und übergibt ihn dem Invoker, um die gewünschte Aktion auszuführen.

Ausgabe:

Fernseher eingeschaltet
Fernseher ausgeschaltet

Welche Informationen sind im Command Pattern enthalten?

Im Command Pattern sind folgende wesentliche Informationen enthalten:

  1. Empfänger (Receiver):
    Das Objekt, das die tatsächliche Arbeit ausführt. Es enthält die spezifische Logik, die durch den Befehl ausgelöst wird. Der Empfänger weiß, wie die Aufgabe ausgeführt wird, aber er weiß nicht, welcher Befehl ihn ausführt.
  2. Befehl (Command):
    Das Command ist ein Objekt, das die Anforderung an den Empfänger kapselt. Es enthält die Informationen, die notwendig sind, um die Aufgabe durchzuführen. Ein Befehl umfasst in der Regel eine execute()-Methode, die ausgeführt wird, um eine bestimmte Aktion auf dem Empfänger auszuführen. In der Regel wird der Befehl auf eine Art und Weise implementiert, dass die Methode execute() den Empfänger informiert und die gewünschte Aktion ausführt.
  3. Konkrete Befehle (ConcreteCommand):
    Diese Klassen erben von der allgemeinen Befehlsschnittstelle und definieren die spezifische Aktion, die bei der Ausführung des Befehls durch den Empfänger durchgeführt wird. Die konkreten Befehle setzen die execute()-Methode um und rufen die entsprechenden Methoden des Empfängers auf.
  4. Invoker:
    Der Invoker ist das Objekt, das den Befehl verwaltet und ihn aufruft. Der Invoker enthält eine Referenz auf das Command-Objekt und ruft dessen execute()-Methode auf. Der Invoker weiß nicht, wie die Arbeit tatsächlich erledigt wird, sondern überlässt dies dem Befehl und dem Empfänger.
  5. Client:
    Der Client ist das Objekt, das die konkreten Befehle erstellt und sie dem Invoker zuweist. Der Client stellt sicher, dass der Invoker die richtigen Befehle erhält und kann auch verschiedene Kombinationen von Befehlen zusammenstellen.

Zusammenfassung der enthaltenen Informationen:

  • Empfänger (Receiver): Enthält die Logik, die ausgeführt wird.
  • Befehl (Command): Kapselt die Anfrage, enthält in der Regel die execute()-Methode.
  • Konkrete Befehle (ConcreteCommand): Implementieren die spezifische Ausführung der execute()-Methode.
  • Invoker: Führt den Befehl aus, kennt jedoch nicht die Details der Ausführung.
  • Client: Erzeugt die Befehle und weist sie dem Invoker zu.

Vorteile des Command Patterns

  1. Entkopplung von Sender und Empfänger: Der Sender muss nichts über den Empfänger oder die Art der Anfrage wissen. Er ruft lediglich die execute()-Methode auf.
  2. Erweiterbarkeit: Neue Befehle können leicht hinzugefügt werden, ohne bestehende Code zu ändern. Man kann einfach neue konkrete Befehlsklassen erstellen.
  3. Rückgängig machen von Befehlen: Das Command Pattern kann leicht angepasst werden, um Befehle rückgängig zu machen oder zu wiederholen. Dies macht das Muster für Undo/Redo-Funktionen nützlich.
  4. Protokollierung: Alle Befehle können als Objekte behandelt und protokolliert werden. Dies kann bei der Überwachung und Fehlerbehebung helfen.

Nachteile des Command Patterns

  1. Erhöhter Aufwand: Das Muster führt zu einer größeren Anzahl von Klassen. Jede neue Anfrage benötigt eine neue konkrete Befehlsklasse.
  2. Komplexität: Wenn zu viele Befehlsobjekte erstellt werden, kann die Architektur unübersichtlich werden. Es ist wichtig, das Muster sinnvoll zu verwenden.

Wann sollte man einen Command Pattern einsetzen?

Das Command Pattern sollte in den folgenden Szenarien eingesetzt werden:

1. Wenn Sie eine Anforderung an ein Objekt senden möchten, ohne zu wissen, welche Methode tatsächlich ausgeführt wird:

  • Das Command Pattern entkoppelt den Sender einer Anfrage (Invoker) von der Ausführung der Anfrage (Receiver). Dies ist besonders nützlich, wenn der Sender nicht wissen soll, welche spezifische Methode aufgerufen wird.
  • Beispiel: Ein Benutzerinterface (UI), das eine Vielzahl von Aktionen auf verschiedenen Objekten durchführen kann, ohne zu wissen, welche konkrete Methode aufgerufen wird.

2. Wenn Sie Befehle in Warteschlangen oder Auftragslisten speichern müssen:

  • Das Command Pattern ermöglicht es, Befehle in einer Warteschlange zu speichern und später auszuführen. Dies ist hilfreich, wenn Befehle asynchron oder zu einem späteren Zeitpunkt ausgeführt werden müssen.
  • Beispiel: Ein System, das eine Warteschlange von Druckaufträgen verwaltet und diese später in der Reihenfolge ihrer Ankunft ausführt.

3. Wenn Sie Undo/Redo-Funktionalität benötigen:

  • Das Command Pattern eignet sich gut für die Implementierung von Undo- und Redo-Funktionen, weil jeder Befehl in einem Objekt gekapselt ist. Um eine Aktion rückgängig zu machen, kann der Befehl einfach in umgekehrter Reihenfolge ausgeführt werden.
  • Beispiel: Ein Texteditor, der es dem Benutzer erlaubt, Textänderungen rückgängig zu machen und wiederherzustellen.

4. Wenn Sie eine komplexe Steuerung von Aktionen benötigen, die miteinander kombiniert werden sollen:

  • Durch das Command Pattern können komplexe Sequenzen von Befehlen als einzelne Einheiten behandelt werden. Sie können Befehle in Makro-Befehlen kombinieren oder Befehle zur Laufzeit dynamisch zusammenstellen.
  • Beispiel: Ein Steuerungssystem für ein Gebäude, das Licht, Temperatur und andere Geräte in einer bestimmten Reihenfolge ansteuern muss.

5. Wenn Sie eine flexible Steuerung von Remote-Aktionen benötigen:

  • Mit dem Command Pattern können Sie Remote-Steuerbefehle abstrahieren. Der Sender muss nicht wissen, wo oder wie der Befehl ausgeführt wird.
  • Beispiel: Eine Fernbedienung für ein Smart Home, die unterschiedliche Geräte steuern kann, ohne dass der Benutzer die Details der Kommunikation kennen muss.

6. Wenn Sie die Anforderung zur Entkopplung von Sender und Empfänger haben:

  • Das Command Pattern hilft dabei, die Verantwortlichkeiten zwischen Sender und Empfänger zu trennen. Dies ist besonders nützlich, wenn das System erweiterbar oder modular gestaltet werden soll, ohne dass die Interaktion zwischen den Komponenten zu stark gekoppelt ist.
  • Beispiel: In einem System zur Steuerung von Maschinen könnte der Operator (Sender) die Maschinen (Empfänger) steuern, ohne sich um die Details der Maschinensteuerung kümmern zu müssen.

7. Wenn Sie mit verschiedenen Benutzern oder Komponenten arbeiten, die unterschiedliche Operationen auf denselben Objekten durchführen sollen:

  • Das Command Pattern kann verwendet werden, um eine Vielzahl von unterschiedlichen Operationen zu kapseln, sodass jede Benutzerinteraktion oder jede Anwendungskomponente ihren eigenen Befehl zur Ausführung bereitstellen kann.
  • Beispiel: In einem grafischen Design-Tool könnte jeder Benutzer ein eigenes Makro erstellen, das eine Serie von Operationen auf einem Bild ausführt.

Zusammenfassung

Das Command Pattern ist besonders nützlich, wenn:

  • Sie die Entkopplung von Sender und Empfänger wünschen,
  • Sie eine flexible, erweiterbare Möglichkeit benötigen, um Befehle auszuführen,
  • Sie eine Undo/Redo-Funktionalität oder eine Warteschlange von Befehlen benötigen,
  • Sie komplexe Abfolgen von Aktionen oder eine Verarbeitung von Anfragen zu späterem Zeitpunkt ermöglichen möchten.

Welche Probleme löst das Command Pattern?

Das Command Pattern löst mehrere Probleme im Zusammenhang mit der Entkopplung von Sendern und Empfängern von Anfragen, der Flexibilität bei der Ausführung von Befehlen und der Verwaltung von komplexen Interaktionen zwischen verschiedenen Komponenten eines Systems. Hier sind die Hauptprobleme, die durch das Command Pattern gelöst werden:

1. Entkopplung von Sender und Empfänger (Verborgene Implementierungsdetails):

  • Problem: Der Sender einer Anfrage muss oft wissen, wie und wo die Anfrage ausgeführt wird (z. B. welche Methode eines Objekts aufgerufen werden muss).
  • Lösung: Das Command Pattern kapselt die Anfrage in einem Befehl, sodass der Sender nicht wissen muss, wie der Empfänger die Anfrage verarbeitet. Der Sender ruft lediglich die execute()-Methode des Befehls auf, ohne sich um die Implementierung kümmern zu müssen. Der Empfänger bleibt unsichtbar für den Sender, was die Kopplung zwischen den beiden reduziert.

Beispiel: Ein UI-Element (wie ein Button) sendet einen Befehl an das System, ohne zu wissen, welche konkrete Methode oder welches Objekt den Befehl ausführt.

2. Flexible und wiederverwendbare Befehle:

  • Problem: Bei einer Vielzahl von möglichen Operationen, die immer wieder ausgeführt werden müssen, kann der Code schnell unübersichtlich und dupliziert werden.
  • Lösung: Das Command Pattern ermöglicht es, alle Anfragen als eigenständige Objekte zu behandeln, die wiederverwendet und flexibel kombiniert werden können. Neue Befehle können problemlos eingeführt werden, ohne dass bestehende Codebasis verändert werden muss.

Beispiel: Ein Druckauftragssystem, das verschiedene Druckbefehle verwalten kann (Drucken, Pausieren, Fortsetzen), ohne dass der Code für jeden Druckbefehl speziell angepasst werden muss.

3. Undo/Redo-Funktionalität (Rückgängig machen von Aktionen):

  • Problem: Viele Anwendungen erfordern eine Undo- und Redo-Funktionalität, aber die Verwaltung dieser Operationen kann komplex sein, besonders wenn verschiedene Aufgaben miteinander kombiniert werden.
  • Lösung: Das Command Pattern kann die Möglichkeit bieten, jede durchgeführte Aktion als Befehl zu kapseln, der umkehrbar ist (z. B. durch Implementierung von undo()– und redo()-Methoden). Das macht es einfach, die Ausführung von Befehlen zu überwachen und rückgängig zu machen.

Beispiel: Ein Texteditor, der alle Änderungen als Befehle speichert und bei Bedarf die letzte Änderung rückgängig machen kann.

4. Verwaltung von asynchronen oder verzögerten Befehlen:

  • Problem: In Systemen, in denen Befehle zu einem späteren Zeitpunkt oder in einer anderen Reihenfolge ausgeführt werden müssen, kann es schwierig sein, die Reihenfolge und das Timing der Befehlsausführung zu verwalten.
  • Lösung: Das Command Pattern ermöglicht es, Befehle zu speichern und diese später auszuführen. Befehle können in Warteschlangen gehalten und in der gewünschten Reihenfolge ausgeführt werden, unabhängig davon, wann sie ursprünglich abgegeben wurden.

Beispiel: Ein System zur Steuerung von Maschinen, bei dem die Befehle in der Reihenfolge ihrer Ankunft ausgeführt werden, auch wenn die Maschinen unterschiedliche Geschwindigkeiten haben.

5. Kombination und Wiederverwendung von Befehlen:

  • Problem: Wenn ein System viele unterschiedliche Operationen benötigt, kann es schwierig werden, diese Operationen in einer Weise zu organisieren, die leicht zu erweitern oder zu ändern ist.
  • Lösung: Das Command Pattern ermöglicht es, Befehle zu kombinieren, zu gruppieren oder zu verschachteln, um komplexere Operationen aus einfachen Befehlen zu erstellen. Man kann auch Makro-Befehle erstellen, die eine Reihe von Aktionen kapseln.

Beispiel: In einem Smart-Home-System können Sie komplexe Makro-Befehle erstellen, wie z. B. „Guten Morgen“, der Lichter, Vorhänge und den Thermostat steuert – all das wird durch das Kombinieren von einfachen Befehlen ermöglicht.

6. Erweiterbarkeit des Systems ohne direkte Änderungen an bestehenden Code:

  • Problem: Wenn das System ständig neue Anforderungen hat, müssen neue Aktionen oft ohne Änderungen am bestehenden Code hinzugefügt werden. Das führt zu einer hohen Wartungskomplexität.
  • Lösung: Neue Befehle können problemlos hinzugefügt werden, indem einfach neue Command-Klassen erstellt werden. Der Invoker muss nicht angepasst werden, solange die Schnittstellen des Commands konsistent bleiben.

Beispiel: In einer GUI-Anwendung könnten neue Aktionen hinzugefügt werden, ohne dass die Logik für bestehende Befehle geändert werden muss.

7. Verwaltung von verschiedenen Arten von Clients und deren Anfragen:

  • Problem: In einem System gibt es oft verschiedene Clients, die unterschiedliche Operationen auf denselben Objekten ausführen. Diese Clients müssen eine Methode zur Interaktion mit den Objekten haben, ohne zu wissen, welche genauen Methoden sie aufrufen.
  • Lösung: Das Command Pattern kapselt die Operationen der Clients in Befehlen. Dies ermöglicht eine einfache Handhabung der verschiedenen Clients und ihre Interaktionen mit den Empfängern, ohne dass deren Details direkt miteinander in Verbindung stehen müssen.

Beispiel: Ein Server, der Anfragen von verschiedenen Clients entgegennimmt und die entsprechenden Befehle für jeden Client ausführt.

Zusammenfassung

Das Command Pattern löst vor allem die Probleme der Entkopplung, der Verwaltung von Befehlen, der Flexibilität bei der Befehlsausführung und der Erweiterbarkeit eines Systems. Es ermöglicht eine klare Trennung der Verantwortlichkeiten und erleichtert die Erweiterung von Anwendungen durch die Kapselung von Anfragen als Objekte.

Fazit

Das Command Pattern ist eine ausgezeichnete Wahl, wenn Sie die Entkopplung von Sendern und Empfängern erreichen und die Flexibilität erhöhen möchten. Es eignet sich besonders für Systeme, bei denen das Verhalten zur Laufzeit geändert oder Befehle rückgängig gemacht werden müssen. Das Beispiel in C++ zeigt, wie das Muster in der Praxis funktioniert und wie einfach es ist, neue Befehle hinzuzufügen.

Zurück zur Liste der Pattern: Liste der Design-Pattern

com

Newsletter Anmeldung

Bleiben Sie informiert! Wir informieren Sie über alle neuen Beiträge (max. 1 Mail pro Woche – versprochen)