Befehlsmuster - Command pattern
In der objektorientierten Programmierung ist das Befehlsmuster ein Verhaltensentwurfsmuster , bei dem ein Objekt verwendet wird, um alle Informationen zu kapseln, die zum Ausführen einer Aktion oder zum Auslösen eines Ereignisses zu einem späteren Zeitpunkt erforderlich sind. Diese Informationen umfassen den Methodennamen, das Objekt, das die Methode besitzt, und Werte für die Methodenparameter.
Vier Begriffe, die immer mit dem Befehlsmuster verbunden sind , sind Befehl , Empfänger , Aufrufer und Client . Ein Befehl Objekt kennt Empfänger und ruft eine Methode des Empfängers. Werte für Parameter der Empfängermethode werden im Befehl gespeichert. Das Empfängerobjekt zum Ausführen dieser Methoden wird ebenfalls durch Aggregation im Befehlsobjekt gespeichert . Der Empfänger erledigt dann die Arbeit, wenn die execute()Methode in command aufgerufen wird. Ein Aufruferobjekt weiß, wie ein Befehl ausgeführt wird, und führt optional eine Buchhaltung über die Befehlsausführung durch. Der Aufrufer weiß nichts über einen konkreten Befehl, es kennt nur über die Befehlsschnittstelle . Aufrufer-Objekt(e), Befehlsobjekte und Empfänger-Objekte werden von einem Client- Objekt gehalten, der Client entscheidet, welche Empfänger-Objekte er den Befehlsobjekten zuweist und welche Befehle er dem Aufrufer zuweist. Der Client entscheidet, welche Befehle an welchen Stellen ausgeführt werden. Um einen Befehl auszuführen, übergibt es das Befehlsobjekt an das Aufruferobjekt.
Die Verwendung von Befehlsobjekten macht es einfacher, allgemeine Komponenten zu konstruieren, die Methodenaufrufe zu einem Zeitpunkt ihrer Wahl delegieren, sequenzieren oder ausführen müssen, ohne die Klasse der Methode oder die Methodenparameter kennen zu müssen. Die Verwendung eines Aufrufer-Objekts ermöglicht eine bequeme Durchführung einer Buchführung über Befehlsausführungen sowie das Implementieren verschiedener Modi für Befehle, die durch das Aufrufer-Objekt verwaltet werden, ohne dass der Client sich der Existenz von Buchführung oder Modi bewusst sein muss.
Die zentralen Ideen dieses Entwurfsmusters spiegeln stark die Semantik erstklassiger Funktionen und Funktionen höherer Ordnung in funktionalen Programmiersprachen wider . Insbesondere ist das Aufruferobjekt eine Funktion höherer Ordnung, von der das Befehlsobjekt ein erstklassiges Argument ist.
Überblick
Das Befehlsentwurfsmuster ist eines der dreiundzwanzig bekannten GoF-Entwurfsmuster, die beschreiben, wie wiederkehrende Entwurfsprobleme gelöst werden, um flexible und wiederverwendbare objektorientierte Software zu entwerfen, dh Objekte, die einfacher zu implementieren, zu ändern, zu testen und zu testen sind Wiederverwendung.
Die Verwendung des Befehlsentwurfsmusters kann diese Probleme lösen:
- Das Koppeln des Aufrufers einer Anfrage an eine bestimmte Anfrage sollte vermieden werden. Das heißt, fest verdrahtete Anforderungen sollten vermieden werden.
- Es sollte möglich sein, ein Objekt (das eine Anfrage aufruft) mit einer Anfrage zu konfigurieren.
Das Implementieren (Festverdrahten) einer Anfrage direkt in eine Klasse ist unflexibel, da es die Klasse zur Kompilierzeit an eine bestimmte Anfrage koppelt, was es unmöglich macht, eine Anfrage zur Laufzeit anzugeben.
Die Verwendung des Befehlsentwurfsmusters beschreibt die folgende Lösung:
- Definieren Sie separate (Befehls-)Objekte, die eine Anforderung 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 verwendet wird, um eine Anforderung auszuführen. Die Klasse ist nicht mehr an eine bestimmte Anfrage gekoppelt und hat keine Kenntnis (ist unabhängig) davon, wie die Anfrage ausgeführt wird.
Siehe auch das UML-Klassen- und Sequenzdiagramm unten.
Struktur
UML-Klassen- und Sequenzdiagramm
Im obigen UML -KlassendiagrammInvoker implementiert die Klasse eine Anforderung nicht direkt. InvokerBezieht sich stattdessen auf die CommandSchnittstelle zum Durchführen einer Anfrage ( command.execute()), die das Invokerunabhängig davon macht, wie die Anfrage ausgeführt wird. Die Command1Klasse implementiert die CommandSchnittstelle, indem sie eine Aktion für einen Empfänger ausführt ( receiver1.action1()).
Das UML- Sequenzdiagramm
zeigt die Laufzeitinteraktionen: Das InvokerObjekt ruft execute()ein Command1Objekt auf.
Command1ruft action1()ein Receiver1Objekt auf, das die Anfrage ausführt.
UML-Klassendiagramm
Verwendet
- GUI-Tasten und Menüpunkte
- In der Swing- und Borland-Delphi- Programmierung
Actionist an ein Befehlsobjekt. Neben der Möglichkeit, den gewünschten Befehl auszuführen, kann eine Aktion ein zugehöriges Symbol, eine Tastenkombination, QuickInfo-Text usw. aufweisen. Eine Symbolleistenschaltfläche oder eine Menüelementkomponente kann nur mit dem Action- Objekt vollständig initialisiert werden . - Makro - Aufnahme
- Wenn alle Benutzeraktionen durch Befehlsobjekte dargestellt werden, kann ein Programm eine Folge von Aktionen aufzeichnen, indem es einfach eine Liste der Befehlsobjekte führt, während sie ausgeführt werden. Es kann dann die gleichen Aktionen "wiedergeben", indem es die gleichen Befehlsobjekte erneut nacheinander ausführt. Wenn das Programm eine Skript-Engine einbettet, kann jedes Befehlsobjekt eine toScript()- Methode implementieren , und Benutzeraktionen können dann einfach als Skripte aufgezeichnet werden.
- Handy-Code
- Unter Verwendung von Sprachen wie Java, in denen Code über URLClassloader und Codebases von einem Ort zum anderen gestreamt/geschlürft werden kann, können die Befehle es ermöglichen, neues Verhalten an entfernte Orte zu liefern (EJB-Befehl, Master Worker).
- Rückgängig auf mehreren Ebenen
- Wenn alle Benutzeraktionen in einem Programm als Befehlsobjekte implementiert sind, kann das Programm einen Stapel der zuletzt ausgeführten Befehle führen. Wenn der Benutzer einen Befehl rückgängig machen möchte, öffnet das Programm einfach das neueste Befehlsobjekt und führt seine undo()- Methode aus.
- Vernetzung
- Es ist möglich, ganze Befehlsobjekte über das Netzwerk zu senden, die auf den anderen Maschinen ausgeführt werden, beispielsweise Spieleraktionen in Computerspielen.
- Parallelverarbeitung
- Wenn die Befehle als Aufgaben in eine gemeinsam genutzte Ressource geschrieben und von vielen Threads parallel ausgeführt werden (möglicherweise auf Remotecomputern; diese Variante wird häufig als Master / Worker-Muster bezeichnet).
- Fortschrittsbalken
- Angenommen, ein Programm hat eine Folge von Befehlen, die es der Reihe nach ausführt. Wenn jedes Befehlsobjekt über eine getEstimatedDuration () -Methode verfügt, kann das Programm die Gesamtdauer leicht schätzen. Es kann einen Fortschrittsbalken anzeigen, der aussagekräftig widerspiegelt, wie nah das Programm am Abschluss aller Aufgaben ist.
- Thread-Pools
- Eine typische Allzweck-Thread-Pool-Klasse kann eine öffentliche addTask()- Methode haben, die eine Arbeitsaufgabe zu einer internen Warteschlange von Aufgaben hinzufügt, die darauf warten, erledigt zu werden. Es verwaltet einen Pool von Threads, die Befehle aus der Warteschlange ausführen. Die Elemente in der Warteschlange sind Befehlsobjekte. Normalerweise implementieren diese Objekte eine gemeinsame Schnittstelle wie java.lang.Runnable , die es dem Thread-Pool ermöglicht, den Befehl auszuführen, obwohl die Thread-Pool-Klasse selbst ohne Kenntnis der spezifischen Aufgaben geschrieben wurde, für die sie verwendet werden würde.
- Transactional Verhalten
- Ähnlich wie beim Rückgängigmachen kann eine Datenbank-Engine oder ein Software-Installationsprogramm eine Liste von Operationen führen, die ausgeführt wurden oder ausgeführt werden. Sollte einer von ihnen fehlschlagen, können alle anderen rückgängig gemacht oder verworfen werden (normalerweise als Rollback bezeichnet ). Wenn beispielsweise zwei aufeinander verweisende Datenbanktabellen aktualisiert werden müssen und die zweite Aktualisierung fehlschlägt, kann die Transaktion zurückgesetzt werden, damit die erste Tabelle jetzt keine ungültige Referenz enthält.
- Zauberer
- Häufig präsentiert ein Assistent mehrere Konfigurationsseiten für eine einzelne Aktion, die nur ausgeführt wird, wenn der Benutzer auf der letzten Seite auf die Schaltfläche "Fertig stellen" klickt. In diesen Fällen besteht eine natürliche Möglichkeit zum Trennen des Benutzeroberflächencodes vom Anwendungscode darin, den Assistenten mithilfe eines Befehlsobjekts zu implementieren. Das Befehlsobjekt wird erstellt, wenn der Assistent zum ersten Mal angezeigt wird. Jede Seite des Assistenten speichert ihre GUI-Änderungen im Befehlsobjekt, sodass das Objekt im Verlauf des Benutzers gefüllt wird. "Finish" löst einfach einen Aufruf von execute() aus . Auf diese Weise funktioniert die Befehlsklasse.
Terminologie
Die zur Beschreibung von Befehlsmusterimplementierungen verwendete Terminologie ist nicht konsistent und kann daher verwirrend sein. Dies ist das Ergebnis von Mehrdeutigkeit , der Verwendung von Synonymen und Implementierungen, die das ursprüngliche Muster möglicherweise verdecken, indem sie weit darüber hinausgehen.
- Mehrdeutigkeit.
- Der Begriff Befehl ist mehrdeutig. Zum Beispiel kann sich nach oben bewegen , nach oben bewegen sich auf einen einzelnen (nach oben bewegen) Befehl beziehen, der zweimal ausgeführt werden sollte, oder es kann sich auf zwei Befehle beziehen, von denen jeder zufällig dasselbe tut (nach oben bewegen). Wenn der vorherige Befehl zweimal zu einem Rückgängig-Stack hinzugefügt wird, beziehen sich beide Elemente auf dem Stack auf dieselbe Befehlsinstanz. Dies kann sinnvoll sein, wenn ein Befehl immer auf die gleiche Weise rückgängig gemacht werden kann (zB nach unten bewegen). Sowohl die Gang of Four als auch das Java-Beispiel unten verwenden diese Interpretation des Begriffs Befehl . Andererseits, wenn die letzteren Befehle zu einem Rückgängig-Stack hinzugefügt werden, bezieht sich der Stack auf zwei separate Objekte. Dies kann angebracht sein, wenn jedes Objekt auf dem Stapel Informationen enthalten muss, mit denen der Befehl rückgängig gemacht werden kann. Um beispielsweise einen Auswahllöschbefehl rückgängig zu machen , kann das Objekt eine Kopie des gelöschten Textes enthalten, damit er wieder eingefügt werden kann, wenn der Auswahllöschbefehl rückgängig gemacht werden muss. Beachten Sie, dass die Verwendung eines separaten Objekts für jeden Aufruf eines Befehls auch ein Beispiel für das Muster der Verantwortungskette ist .
- Auch der Begriff Ausführen ist mehrdeutig. Es verweisen kann durch den Befehl des Objekts identifiziert den Code ausgeführt wird ausführen Methode. In Microsoft jedoch Windows Presentation Foundation wird ein Befehl betrachtet ausgeführt wurden , wenn der Befehl ausführen Methode aufgerufen wurde, aber das bedeutet nicht unbedingt , dass der Anwendungscode ausgeführt wurde. Dies geschieht erst nach einer weiteren Ereignisverarbeitung.
- Synonyme und Homonyme .
- Client, Quelle, Aufrufer : die Schaltfläche, die Schaltfläche in der Symbolleiste oder das Menüelement, das angeklickt wurde, die vom Benutzer gedrückte Tastenkombination.
- Command-Objekt, Routed-Command-Objekt, Action-Objekt : ein Singleton-Objekt (zB gibt es nur ein CopyCommand-Objekt), das Tastenkombinationen, Schaltflächenbilder, Befehlstext usw. in Bezug auf den Befehl kennt. Ein Quell-/Aufrufer-Objekt ruft die Methode execute/performAction des Command/Action-Objekts auf. Das Befehls-/Aktionsobjekt benachrichtigt die entsprechenden Quell-/Aufruferobjekte, wenn sich die Verfügbarkeit eines Befehls/einer Aktion geändert hat. Dadurch können Schaltflächen und Menüelemente inaktiv (abgeblendet) werden, wenn ein Befehl / eine Aktion nicht ausgeführt / ausgeführt werden kann.
- Empfänger, Zielobjekt: das Objekt, das kopiert, eingefügt, verschoben usw. werden soll. Das Empfängerobjekt besitzt die Methode, die von der Methode execute des Befehls aufgerufen wird . Der Empfänger ist typischerweise auch das Zielobjekt. Wenn das Empfängerobjekt beispielsweise ein Cursor ist und die Methode moveUp heißt , würde man erwarten, dass der Cursor das Ziel der moveUp-Aktion ist. Andererseits, wenn der Code durch das Befehlsobjekt selbst definiert wird, ist das Zielobjekt ein völlig anderes Objekt.
- Befehlsobjekt, weitergeleitete Ereignisargumente, Ereignisobjekt : das Objekt, das von der Quelle an das Befehls-/Aktionsobjekt, an das Zielobjekt an den Code übergeben wird, der die Arbeit erledigt. Jeder Tastenklick oder jede Tastenkombination führt zu einem neuen Befehls- / Ereignisobjekt. Einige Implementierungen fügen dem Befehls-/Ereignisobjekt weitere Informationen hinzu, wenn es von einem Objekt (zB CopyCommand) an ein anderes (zB Dokumentabschnitt) weitergegeben wird. Bei anderen Implementierungen werden Befehls- / Ereignisobjekte in andere Ereignisobjekte (z. B. eine Box in einer größeren Box) eingefügt, wenn sie sich entlang der Linie bewegen, um Namenskonflikte zu vermeiden. (Siehe auch Muster der Verantwortungskette .)
- Handler, ExecutedRoutedEventHandler, Methode, Funktion : der eigentliche Code, der das Kopieren, Einfügen, Verschieben usw. durchführt. In einigen Implementierungen ist der Handler-Code Teil des Befehls-/Aktionsobjekts. In anderen Implementierungen ist der Code Teil des Empfänger-/Zielobjekts, und in noch anderen Implementierungen wird der Handler-Code von den anderen Objekten getrennt gehalten.
- Command Manager, Undo Manager, Scheduler, Queue, Dispatcher, Invoker : ein Objekt, das Befehls-/Ereignisobjekte auf einen Rückgängig- oder Wiederherstellungsstapel legt oder Befehls-/Ereignisobjekte festhält, bis andere Objekte bereit sind, darauf zu reagieren, oder die die Befehls-/Ereignisobjekte an das entsprechende Empfänger-/Zielobjekt oder Handlercode weiterleitet.
- Implementierungen, die weit über das ursprüngliche Befehlsmuster hinausgehen.
- Microsofts Windows Presentation Foundation (WPF) führt geroutete Befehle ein, die das Befehlsmuster mit der Ereignisverarbeitung kombinieren. Dadurch enthält das Befehlsobjekt weder einen Verweis auf das Zielobjekt noch einen Verweis auf den Anwendungscode mehr. Stattdessen Aufrufe das Befehlsobjekt auszuführen Befehl führt zu einem sogenannten Routed Ereignisse ausgeführt , dass während der Tunnelung des Ereignisses oder Einblasen eines auftreten kann sogenannte Bindungs Objekt , das identifiziert das Ziel und der Anwendungscode, der an diesem Punkt ausgeführt wird.
Beispiel
Betrachten Sie einen "einfachen" Schalter. In diesem Beispiel konfigurieren wir den Schalter mit zwei Befehlen: zum Einschalten des Lichts und zum Ausschalten des Lichts.
Ein Vorteil dieser speziellen Implementierung des Befehlsmusters besteht darin, dass der Schalter mit jedem Gerät verwendet werden kann, nicht nur mit einem Licht. Der Switch in der folgenden C#-Implementierung schaltet ein Licht ein und aus, aber der Konstruktor von Switch kann alle Unterklassen von Command für seine beiden Parameter akzeptieren. Sie können beispielsweise den Switch so konfigurieren, dass eine Engine gestartet wird.
using System;
namespace CommandPattern
{
public interface ICommand
{
void Execute();
}
/* The Invoker class */
public class Switch
{
ICommand _closedCommand;
ICommand _openedCommand;
public Switch(ICommand closedCommand, ICommand openedCommand)
{
this._closedCommand = closedCommand;
this._openedCommand = openedCommand;
}
// Close the circuit / power on
public void Close()
{
this._closedCommand.Execute();
}
// Open the circuit / power off
public void Open()
{
this._openedCommand.Execute();
}
}
/* An interface that defines actions that the receiver can perform */
public interface ISwitchable
{
void PowerOn();
void PowerOff();
}
/* The Receiver class */
public class Light : ISwitchable
{
public void PowerOn()
{
Console.WriteLine("The light is on");
}
public void PowerOff()
{
Console.WriteLine("The light is off");
}
}
/* The Command for turning on the device - ConcreteCommand #1 */
public class CloseSwitchCommand : ICommand
{
private ISwitchable _switchable;
public CloseSwitchCommand(ISwitchable switchable)
{
_switchable = switchable;
}
public void Execute()
{
_switchable.PowerOn();
}
}
/* The Command for turning off the device - ConcreteCommand #2 */
public class OpenSwitchCommand : ICommand
{
private ISwitchable _switchable;
public OpenSwitchCommand(ISwitchable switchable)
{
_switchable = switchable;
}
public void Execute()
{
_switchable.PowerOff();
}
}
/* The test class or client */
internal class Program
{
public static void Main(string[] arguments)
{
string argument = arguments.Length > 0 ? arguments[0].ToUpper() : null;
ISwitchable lamp = new Light();
// Pass reference to the lamp instance to each command
ICommand switchClose = new CloseSwitchCommand(lamp);
ICommand switchOpen = new OpenSwitchCommand(lamp);
// Pass reference to instances of the Command objects to the switch
Switch @switch = new Switch(switchClose, switchOpen);
if (argument == "ON")
{
// Switch (the Invoker) will invoke Execute() on the command object.
@switch.Close();
}
else if (argument == "OFF")
{
// Switch (the Invoker) will invoke the Execute() on the command object.
@switch.Open();
}
else
{
Console.WriteLine("Argument \"ON\" or \"OFF\" is required.");
}
}
}
}
Siehe auch
- Stapelwarteschlange
- Schließung
- Befehlswarteschlange
- Funktionsobjekt
- Job-Scheduler
- Model View Controller
- Prioritätswarteschlange
- Software-Designmuster
- GoF – Designmuster
Quellen
Die erste veröffentlichte Erwähnung der Verwendung einer Command-Klasse zur Implementierung interaktiver Systeme scheint ein 1985 erschienener Artikel von Henry Lieberman zu sein. Die erste veröffentlichte Beschreibung eines (mehrstufigen) Rückgängig-Wiederherstellungsmechanismus unter Verwendung einer Befehlsklasse mit Ausführungs- und Rückgängig- Methoden und einer Verlaufsliste scheint die erste (1988) Ausgabe von Bertrand Meyers Buch Objektorientierte Software zu sein Konstruktion , Abschnitt 12.2.
