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:
- Command (Befehl): Eine abstrakte Klasse oder Schnittstelle, die eine Methode zur Ausführung des Befehls definiert.
- ConcreteCommand (Konkreter Befehl): Eine Implementierung der
Command
-Schnittstelle, die eine spezifische Anfrage repräsentiert und den Empfänger aufruft, um die Anfrage auszuführen. - 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.
- 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.
- 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
- Receiver (Empfänger): In diesem Beispiel haben wir die Klassen
Light
undTV
als Empfänger. Diese Klassen implementieren Methoden zum Ein- und Ausschalten der Geräte. - Command (Befehl): Die abstrakte
Command
-Schnittstelle definiert dieexecute()
-Methode. Jede konkrete Befehlsklasse implementiert diese Methode, um die spezifische Aktion (z. B. das Einschalten von Licht oder TV) auszuführen. - ConcreteCommand (Konkrete Befehle): Die Klassen
LightOnCommand
,LightOffCommand
,TVOnCommand
undTVOffCommand
sind konkrete Implementierungen des Command-Interfaces. Sie kapseln die Anfrage und leiten sie an die entsprechenden Empfänger weiter. - 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. - Client: Der Client erstellt die Empfänger, Befehle und den Aufrufer. Er verbindet alles, sodass der Aufrufer mit den entsprechenden Befehlen ausgestattet ist.
Vorteile des Command Patterns
- 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. - Erweiterbarkeit: Neue Befehle können leicht hinzugefügt werden, ohne bestehende Code zu ändern. Man kann einfach neue konkrete Befehlsklassen erstellen.
- 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.
- Protokollierung: Alle Befehle können als Objekte behandelt und protokolliert werden. Dies kann bei der Überwachung und Fehlerbehebung helfen.
Nachteile des Command Patterns
- Erhöhter Aufwand: Das Muster führt zu einer größeren Anzahl von Klassen. Jede neue Anfrage benötigt eine neue konkrete Befehlsklasse.
- Komplexität: Wenn zu viele Befehlsobjekte erstellt werden, kann die Architektur unübersichtlich werden. Es ist wichtig, das Muster sinnvoll zu verwenden.
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