Muster: Command
Das Kommando oder der Befehl (engl. Command) ist ein Verhaltensmuster (Behavioral Patterns). Es dient zum Kapseln von Anfragen als Kommando-Objekte, um damit Empfänger zu parametrisieren. Anfragen können dabei in Warteschlangen gestellt, aufgezeichnet und später ggf. auch wieder rückgängig gemacht werden.
Auch bekannt als: Aktion, Transaktion (Action, Transaction)
Zweck und Verwendung
[Bearbeiten]Das Kommando-Muster kapselt einen (parametrierbaren) Befehl in ein Objekt um den Aufruf eines Befehls von dessen Ausführung zu entkoppeln. Genauer definiert sich das Muster wie folgt:
- Objekte sollen mit einer auszuführenden Aktion, dem Befehl, parametrisiert werden. Das ist die objektoriertierte Entsprechung zu Rückruffunktionen (eng. callback function). (zB eine Schaltfläche in einer GUI soll mit einer Aktion verknüpft werden)
- Befehle, also auszuführende Aktionen, werden in einzelne Objekte gekapselt. Dies folgt dem objektorientierten Prinzip der Kapselung des Veränderbaren.
- Das Erstellen des Befehls und das tatsächliche Ausführen finden zu verschiedenen Zeiten oder in einem anderen Kontext (Thread, Prozess, Rechner) statt.
UML-Diagramm
[Bearbeiten]Akteure
[Bearbeiten]- Befehl (Command)
- Basisklasse aller Befehle
- definiert die Schnittstelle zum Ausführen des Befehls
- Konkreter Befehl (Concrete Command)
- speichert den zum Ausführen nötigen Zustand, darunter typischerweise auch einen Verweis auf den Empfänger
- implementiert die Befehlsschnittstelle
- Klient (Client)
- erzeugt einen konkreten Befehl und versieht ihn mit einem Verweis auf den Empfänger und allen anderen nötigen Informationen
- gibt dem Aufrufer eine Referenz auf den konkreten Befehl
- Aufrufer(Invoker)
- besitzt einen oder mehrere Verweise auf Befehle
- fordert diese Befehle bei Bedarf auf, ihre Aktion auszuführen
- Empfänger (Receiver)
- Der konkrete Befehl ruft Methoden des Empfängerobjektes auf, um seine Aktion auszuführen.
- An den Empfänger werden keine besonderen Anforderungen gestellt. Er muss nichts über die anderen Akteure wissen. Somit kann jede Klasse als Empfänger dienen.
Anwendungsfälle
[Bearbeiten]Da Befehle in Objekten gekapselt sind, können sie gespeichert, herumgereicht, gruppiert oder modifiziert werden. Dies erlaubt eine Reihe von interessanten Anwendungsmöglichkeiten:
- Implementation einer Warteschlange, bei der die Befehle nacheinander abgearbeitet werden. Dabei sind die Befehle lose an die ausführende Einheit gekoppelt, so dass ein Austauschen, Ändern etc. zur Laufzeit möglich ist.
- Implementation von Logging von Befehlen und einer Wiederherstellung nach einem Systemabsturz.
- Gruppierung von mehreren Befehlsobjekten in einem Makrobefehl, um verschiedene Aktivitäten gebündelt auszuführen.
- Implementation eines Rückgängig-Mechanismus (Undo). Bei jeder Ausführung werden die zur Umkehrung nötigen Daten im Befehls-Objekt gespeichert und das Objekt selber auf einem Stapel gesichert. Um das Gegenteil Wiederherstellen (Redo) zu implementieren, genügt ein zweiter Stapel für die rückgängig gemachten Befehle.
- Implementierung einer zeitliche Entkopplung zwischen Aufruf und Abarbeitung (asynchrone Abarbeitung).
- Parallelisierung zum Beispiel unter Zuhilfenahme eines Master/Worker-Musters: Die einzelnen Arbeitspakete, die von einem Threadpool parallel abgearbeitet werden, können als Kommandos implementiert werden.
Ideen hinter erweiterten Anwendungen
[Bearbeiten]Der Makro-Befehl:
- Ein Makro-Befehl gruppiert eine Menge von Aktionen und führt sie gemeinsam aus. Dabei sind die einzelnen Aktionen in den Befehlsobjekten gekapselt. Idee ist nun, in einem Makro-Befehlsobjekt mehrere Befehlsobjekte zu speichern und diesen in einer vorgegebenen Reihenfolge zu befehlen, die Aktionen auf dem ihnen zugeordnetem Empfängerobjekt auszuführen.
- Der Makro-Befehl implementiert ebenfalls die Schnittstelle "Abstrakter Befehl". Ergänzt wird der Makro-Befehl um eine Liste oder ein Array zur Speicherung einer Reihe von Befehlsobjekten. Bei der Initialisierung (oder später dynamisch zur Laufzeit) wird dem Makro-Befehl ein Array von Befehlsobjekten übergeben.
- In der Methode ausführen(), welche einen Befehl zu Abarbeitung der Aktionen bewegt, iteriert der Makro-Befehl durch das Befehlsarray und ruft für jedes der Befehlsobjekte die Methode ausführen() auf.
- Eine Rückgängig-Funktionalität kann auch hier einfach gewährleistet werden, indem der Makro-Befehl rückwärts durch das Befehlsarray iteriert und auf jedem gespeichertem Befehlsobjekt die rückgängig()-Methode aufruft, die den Zustand des Systems vor der letzten Ausführung der ausführen()-Methode wiederherstellt.
Rückgängig-Funktion für Benutzeroberflächen:
- Konkrete Befehle realisieren dann Aktionen wie Datei öffnen, Rückgängig oder Schreibmarke nach rechts
- Klienten sind die Applikation oder Dialoge.
- Aufrufer sind Schaltflächen, Menüpunkte oder Hotkeys.
- Empfänger sind die Applikation (Datei öffnen) oder das Dokument (Rückgängig, Einfügemarke nach rechts)
Vorteile
[Bearbeiten]- Auslösender und Ausführender sind entkoppelt. Dadurch können Erstellung und Ausführung der Befehle zu anderen Zeiten oder in anderen Kontexten (Threads, Prozesse, Rechner) stattfinden.
- Ausführenden können zur Laufzeit dynamisch neue Befehle übergeben werden.
- Befehlsobjekte können wie andere Objekte auch manipuliert werden (Verändern, Filtern, Zwischenspeichern, etc.).
- Befehlsobjekte können zu komplexen Befehlen bzw. Befehlsketten kombiniert werden (Makros, realisiert als Kompositum).
- Aufrufer sind (syntaktisch) nur von der Befehls-Basisklasse abhängig, nicht von deren konkreten Implementierung. Dies steigert die Wartbarkeit und Flexibilität.
Nachteile
[Bearbeiten]- Es wird für jedes Kommando eine neue Klasse benötigt. Dies kann sehr schnell zu einer großen Menge von Klassen führen.
- Bei sehr "kleinen" (schnell ausführbaren) Befehlen dominieren die Kosten für die Befehlsverwaltung im Gegensatz zu den Kosten der eigentlichen Befehle. Dies kann bei hohen Lasten und performancekritischen Anwendungen evtl. problematisch sein.
Implementation
[Bearbeiten]- Die Kommando-Klasse sollte als eine (abstrakte) Basisklasse implementiert werden (je nach sprachspezifischen Eigenschaften auch als Interface). Siehe UML-Diagramm. Die Applikation arbeitet prinzipiell nur mit der Basisklasse, während die einzelnen (konkreten) Kommando-Klassen nur bei der Instanzierung verwendet werden.
- Die Kommando-Basisklasse stellt je nach Applikation verschiedene Methoden bereit z.B.:
- execute() Diese Methode ist zwingend erforderlich und führt das Kommando aus.
- undo() Macht das bereits ausgeführte Kommando wieder rückgängig
- Von dieser allgemein gängigen Implementierung kann aber auch abgewichen werden. So kann die execute()-Funktion auch als Rückruffunktion (Delegate, callback function) implementiert werden.
- Dieses Muster kann in verschiedener Hinsicht erweitert werden:
- Einführen von Kommando-Typen zur Kategorisierung (zum Beispiel bei unterschiedlichen Abarbeitungsumgebungen verschiedener Kommandotypen). Vor diesem Schritt ist zu überprüfen, ob sich das gewünschte Verhalten auch durch Polymorphismus erreichen lässt!
- Einführung von Kommando-Prioritäten, so dass Kommandos in der die Warteschlange nach Priorität sortiert eingefügt und dementsprechend abgearbeitet werden
Beispiele
[Bearbeiten]Verwandte Muster
[Bearbeiten]- Makro-Befehle können als Kompositum implementiert werden.
- Das Memento-Entwurfsmuster kann die Objektzustände speichern, um eine Rückgängigfunktion zu vereinfachen.