In der objektorientierten Programmierung ist das Befehlsmuster ein Verhaltensmuster , bei dem ein Objekt alle Informationen kapselt , die benötigt werden, um zu einem späteren Zeitpunkt eine Aktion auszuführen oder ein Ereignis auszulösen. Diese Informationen umfassen den Methodennamen, das Objekt, dem die Methode gehört, und die Werte der Methodenparameter.
Vier Begriffe, die stets mit dem Befehlsmuster verbunden sind, sind Befehl , Empfänger , Aufrufer und Client . Ein Befehlsobjekt kennt den Empfänger und ruft dessen Methode auf. Die Parameterwerte der Empfängermethode werden im Befehl gespeichert. Das Empfängerobjekt, das diese Methoden ausführt, wird ebenfalls durch Aggregation im Befehlsobjekt gespeichert . Der Empfänger führt dann die Arbeit aus, wenn die execute()Methode im Befehl aufgerufen wird. Ein Aufruferobjekt weiß, wie ein Befehl ausgeführt wird und kann optional die Befehlsausführung verwalten. Der Aufrufer kennt keine konkreten Befehle, sondern nur die Befehlsschnittstelle . Aufruferobjekte, Befehlsobjekte und Empfängerobjekte werden von einem Clientobjekt verwaltet . Der Client entscheidet, welche Empfängerobjekte er den Befehlsobjekten zuweist und welche Befehle er dem Aufrufer zuweist. Der Client entscheidet, welche Befehle wann ausgeführt werden. Um einen Befehl auszuführen, übergibt er das Befehlsobjekt an das Aufruferobjekt.
Die Verwendung von Befehlsobjekten vereinfacht die Erstellung allgemeiner Komponenten, die Methodenaufrufe delegieren, sequenzieren oder zu einem beliebigen Zeitpunkt ausführen können, ohne dass die Klasse oder die Parameter der Methode bekannt sein müssen. Mit einem Aufruferobjekt lässt sich die Verwaltung von Befehlsausführungen komfortabel durchführen und verschiedene Modi für Befehle implementieren, die vom Aufruferobjekt verwaltet werden, ohne dass der Client von der Verwaltung oder den Modi Kenntnis haben muss.
Die zentralen Ideen dieses Entwurfsmusters spiegeln die Semantik von Funktionen erster Klasse und Funktionen höherer Ordnung in funktionalen Programmiersprachen wider . Genauer gesagt ist das Aufruferobjekt eine Funktion höherer Ordnung, deren Befehlsobjekt ein Argument erster Klasse ist.
Überblick
Das Command -Entwurfsmuster [ 1 ] ist eines der dreiundzwanzig bekannten GoF-Entwurfsmuster , die beschreiben, wie man wiederkehrende Entwurfsprobleme löst, um flexible und wiederverwendbare objektorientierte Software zu entwerfen, also Objekte, die einfacher zu implementieren, zu ändern, zu testen und wiederzuverwenden sind.
Durch die Verwendung des Befehlsentwurfsmusters können diese Probleme gelöst werden: [ 2 ]
- Eine Kopplung des Aufrufers einer Anfrage an eine bestimmte Anfrage sollte vermieden werden. Das heißt, fest codierte Anfragen sollten vermieden werden.
- Es sollte möglich sein, ein Objekt (das eine Anfrage auslöst) mit einer Anfrage zu konfigurieren.
Die direkte Implementierung (Festverdrahtung) einer Anfrage in eine Klasse ist unflexibel, da sie die Klasse zur Kompilierzeit an eine bestimmte Anfrage koppelt, was es unmöglich macht, zur Laufzeit eine Anfrage anzugeben.
Die Verwendung des Befehlsentwurfsmusters beschreibt die folgende Lösung:
- Definiere separate (Befehls-)Objekte, die eine Anfrage kapseln.
- Eine Klasse delegiert eine Anfrage an ein Befehlsobjekt, anstatt eine bestimmte Anfrage direkt zu implementieren.
Dies ermöglicht es, eine Klasse mit einem Befehlsobjekt zu konfigurieren, das zur Ausführung einer Anfrage verwendet wird. Die Klasse ist nicht mehr an eine bestimmte Anfrage gebunden und hat keine Kenntnis davon (ist unabhängig davon), wie die Anfrage ausgeführt wird.
Siehe auch das untenstehende UML-Klassen- und Sequenzdiagramm.
Struktur
UML-Klassen- und Sequenzdiagramm

Im obigen UML- Klassendiagramm implementiert die InvokerKlasse keine Anfrage direkt. Stattdessen Invokerverweist sie auf die CommandSchnittstelle zur Durchführung einer Anfrage ( command.execute()), wodurch Invokerdie Klasse unabhängig davon ist, wie die Anfrage ausgeführt wird. Die Command1Klasse implementiert die CommandSchnittstelle, indem sie eine Aktion auf einem Empfänger ausführt ( receiver1.action1()).
Das UML- Sequenzdiagramm zeigt die Interaktionen zur Laufzeit: Ein InvokerObjekt ruft execute()ein Command1anderes Objekt auf. Command1Dieses ruft wiederum action1()ein Receiver1Objekt auf, welches die Anfrage ausführt.
UML-Klassendiagramm

Verwendung
- GUI-Schaltflächen und Menüpunkte
- In der Swing- und Borland- Delphi- Programmierung
Actionist eine Aktion ein Befehlsobjekt. Neben der Möglichkeit, den gewünschten Befehl auszuführen, kann eine Aktion ein zugehöriges Symbol, eine Tastenkombination , einen Tooltip -Text usw. haben. Eine Schaltfläche in der Symbolleiste oder ein Menüelement kann vollständig allein mithilfe des Aktionsobjekts initialisiert werden . - Makroaufnahme
- Wenn alle Benutzeraktionen durch Befehlsobjekte repräsentiert werden, kann ein Programm eine Aktionssequenz aufzeichnen, indem es einfach eine Liste der ausgeführten Befehlsobjekte führt. Anschließend kann es dieselben Aktionen wiedergeben, indem es die Befehlsobjekte erneut nacheinander ausführt. Verfügt das Programm über eine integrierte Skript-Engine, kann jedes Befehlsobjekt eine `toScript()` -Methode implementieren, sodass Benutzeraktionen einfach als Skripte aufgezeichnet werden können.
- Mobilfunkcode
- Durch die Verwendung von Sprachen wie Java, bei denen Code über URLClassloader und Codebases von einem Ort zum anderen gestreamt/geslurpt werden kann, können die Befehle es ermöglichen, neues Verhalten an entfernten Orten bereitzustellen (EJB-Befehl, Master Worker).
- Mehrstufiges Rückgängigmachen
- Wenn alle Benutzeraktionen in einem Programm als Befehlsobjekte implementiert sind, kann das Programm einen Stapel der zuletzt ausgeführten Befehle verwalten. Wenn der Benutzer einen Befehl rückgängig machen möchte, entfernt das Programm einfach das zuletzt ausgeführte Befehlsobjekt und führt dessen `undo()` -Methode aus.
- Netzwerk
- Es ist möglich, ganze Befehlsobjekte über das Netzwerk zu senden, damit sie auf anderen Rechnern ausgeführt werden, beispielsweise Spieleraktionen in Computerspielen.
- Parallelverarbeitung
- Hierbei werden die Befehle als Aufgaben an eine gemeinsam genutzte Ressource gesendet und von vielen Threads parallel ausgeführt (möglicherweise auf entfernten Rechnern; diese Variante wird oft als Master/Worker-Muster bezeichnet).
- Fortschrittsbalken
- Angenommen, ein Programm führt eine Folge von Befehlen nacheinander aus. Besitzt jedes Befehlsobjekt eine Methode ` getEstimatedDuration()` , kann das Programm die Gesamtdauer leicht abschätzen. Es kann einen Fortschrittsbalken anzeigen, der anschaulich darstellt, wie weit das Programm von der Ausführung aller Aufgaben entfernt ist.
- Thread-Pools
- Eine typische, universelle Thread-Pool-Klasse besitzt möglicherweise eine öffentliche Methode `addTask()` , die ein Arbeitselement in eine interne Warteschlange einfügt. Sie verwaltet einen Pool von Threads, die Befehle aus dieser Warteschlange ausführen. Die Elemente in der Warteschlange sind Befehlsobjekte. Typischerweise implementieren diese Objekte eine gemeinsame Schnittstelle wie ` java.lang.Runnable` , die es dem Thread-Pool ermöglicht, den Befehl auszuführen, selbst wenn die Thread-Pool-Klasse selbst ohne Kenntnis der spezifischen Aufgaben, für die sie verwendet werden soll, entwickelt wurde.
- Transaktionsverhalten
- Ähnlich wie bei der Undo-Funktion kann eine Datenbank-Engine oder ein Software-Installationsprogramm eine Liste der ausgeführten oder noch auszuführenden Operationen führen. Schlägt eine dieser Operationen fehl, können alle anderen rückgängig gemacht oder verworfen werden (üblicherweise als Rollback bezeichnet ). Wenn beispielsweise zwei Datenbanktabellen, die aufeinander verweisen, aktualisiert werden müssen und die zweite Aktualisierung fehlschlägt, kann die Transaktion zurückgesetzt werden, sodass die erste Tabelle nun keine ungültige Referenz mehr enthält.
- Zauberer
- Oftmals präsentiert ein Assistent mehrere Konfigurationsseiten für eine einzige Aktion, die erst ausgeführt wird, wenn der Benutzer auf der letzten Seite auf „Fertigstellen“ klickt. In solchen Fällen bietet es sich an, den Assistenten mithilfe eines Befehlsobjekts zu implementieren, um den Code der Benutzeroberfläche vom Anwendungscode zu trennen. Das Befehlsobjekt wird beim ersten Aufruf des Assistenten erstellt. Jede Seite des Assistenten speichert ihre GUI-Änderungen im Befehlsobjekt, sodass dieses im Verlauf der Benutzerbearbeitung aktualisiert wird. „Fertigstellen“ löst lediglich einen Aufruf von ` execute()` aus . Dadurch funktioniert die Befehlsklasse.
Beispiel
Diese C++23-Implementierung basiert auf der im Buch beschriebenen Implementierung vor C++98.
import std ;using std :: shared_ptr ; using std :: unique_ptr ;// Abstrakte Befehlsklasse Command { protected : Command () = default ; public : // deklariert eine Schnittstelle zur Ausführung einer Operation. virtual void execute () = 0 ; virtual ~ Command () = default ; };// Vorlage für konkrete Befehle < typename Receiver > class SimpleCommand : public Command { private : Receiver * receiver ; Action action ; public : using Action = void ( Receiver ::* )();// Definiert eine Bindung zwischen einem Receiver-Objekt und einer Aktion. SimpleCommand ( shared_ptr <Receiver> receiver , Action action ) : receiver { receiver.get ( ) }, action { action } { }SimpleCommand ( const SimpleCommand & ) = delete ; const SimpleCommand & operator = ( const SimpleCommand & ) = delete ;// Implementiert execute durch Aufruf der entsprechenden Operation(en) auf Receiver. virtual void execute () { ( receiver ->* action )(); } };// Empfängerklasse MyClass { public : // weiß, wie die mit der Ausführung einer Anfrage verbundenen Operationen durchzuführen sind. Jede Klasse kann als Empfänger dienen. void action () { std :: println ( "MyClass::action aufgerufen" ); } };int main ( int argc , char * argv [ ] ) { shared_ptr <MyClass> receiver = std :: make_shared <MyClass> ( ) ; // ... unique_ptr <Command> command = std :: make_unique < SimpleCommand <MyClass> > ( receiver , & MyClass :: action ) ; // ... command- > execute ( ) ; }Die Programmausgabe ist
MyClass :: Aktion aufgerufenSiehe auch
- Batch-Warteschlange
- Schließung
- Befehlswarteschlange
- Funktionsobjekt
- Jobplaner
- Modell-Ansicht-Controller
- Prioritätswarteschlange
- Software-Entwurfsmuster
- Entwurfsmuster (Buch)
Geschichte
Die erste veröffentlichte Erwähnung der Verwendung einer Command-Klasse zur Implementierung interaktiver Systeme scheint ein Artikel von Henry Lieberman aus dem Jahr 1985 zu sein. [ 4 ] Die erste veröffentlichte Beschreibung eines (mehrstufigen) Undo-Redo-Mechanismus unter Verwendung einer Command-Klasse mit execute- und undo- Methoden sowie einer Verlaufsliste findet sich anscheinend in der ersten (1988) Auflage von Bertrand Meyers Buch „ Object-oriented Software Construction“ [ 5 ] , Abschnitt 12.2.
Referenzen
- ^ Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides (1994). Entwurfsmuster: Elemente wiederverwendbarer objektorientierter Software . Addison Wesley. S. 233ff . ISBN 0-201-63361-2Die
- ^ „Das Command-Entwurfsmuster – Problem, Lösung und Anwendbarkeit“ . w3sDesign.com . Archiviert vom Original am 23.09.2020 . Abgerufen am 12.08.2017 .
- ^ „Das Command-Entwurfsmuster – Struktur und Zusammenarbeit“ . w3sDesign.com . Archiviert vom Original am 23. September 2020. Abgerufen am 12. August 2017 .
- ^ Lieberman, Henry (1985). „Menüsysteme sind mehr als nur Bildschirmdarstellung“. ACM SIGGRAPH Computer Graphics . 19 (3): 181–189 . doi : 10.1145/325165.325235 .
- ^ Meyer, Bertrand (1988). Objektorientierte Softwareentwicklung (1. Aufl.). Prentice-Hall. Bibcode : 1988oosc.book.....M .
