Erweitertes Kommandomuster und ekliger dynamic_cast...
-
Command::execute() Executor::execute(Command & cmd); MessageBus::execute(Command & cmd);Bei diesem Code sagt mir meine innere Stimme: Hier stimmt was nicht vom Design her. Executor::execute ist ja arg ... und warum soll der MessageBus Kommandos ausfuehren? Wer fuehrt denn nun was tatsaechlich aus?
double dispatch (in diesem fall IMO total unpassend)
Warum ist double dispatch unpassend?
Sieht jemand eine Möglichkeit, den dynmic_cast rauszuschmeissen?
Tja kommt drauf an, was Command alles ist, ob Mehrfachvererbung genutzt wird und was damit bezweckt werden soll. Normalerweise arbeitet ein "Befehl" innerhalb eines Kontextes und manipuliert oder reagiert auf seine Umgebung. Ganz abstrakt gesprochen: Command::execute( Environment& env ). Was alles in die Environment hinein kommt oder wie das Command das dort herausholt, kann jedes fuer sich entscheiden.
Dann ist natuerlich auch die Frage wie deine Serialisierung/Deserialisierung aussieht, wenn du Commands ueber das Netz schickst. Dort brauchst du ja auch ein "Typsystem". Das kann auch fuer die Unterscheidung herangezogen werden.
-
knivil schrieb:
Bei diesem Code sagt mir meine innere Stimme: Hier stimmt was nicht vom Design her. Executor::execute ist ja arg ... und warum soll der MessageBus Kommandos ausfuehren? Wer fuehrt denn nun was tatsaechlich aus?
Ich könnte des
MessageBusauchCommandBusnennen, und dasexecutevom CommandBus auchdistribute.
Ausgeführt werden die Kommandos von den "Executoren". Deshalb heissen die auch so. Der Auslöser ist dabei der Nachrichtenbus.
Ein Kommando selbst führt beim Kommandomuster üblicherweise nichts aus, sondern es wird ausgeführt. Und zwar im Kontext des Empfängers.
Hier nochmal etwas Lektüre zu dem Thema: Command Pattern
PS: Das "ins Netz schicken" ist für den Nachrichtenbus transparent. Aus seiner Sicht wird nur ein Kommando ausgeführt. Tatsächlich steckt dahinter auch nur eine weitere konkrete Implementierung (quasi
RomoteExecutor) vonExecutor, welche sich um den Netzwerkteil kümmert. Bei den Empfängern wird es wieder zum Kommando deserialisert und in den jeweigen lokalen Nachrichtenbus geschickt.
-
Hmm... Ich habe das Ganze wohl etwas zu vereinfacht angeschaut. Denn wenn es nur darum ginge, für einen Executor zu prüfen, ob der Command der Richtige ist, könnte man in diesem je nach Typ etwas anderes ausführen lassen. Aber dabei habe ich nicht berücksichtigt, dass für jeden Executor eine eigene Behandlung erfolgen muss - man kann die virtuellen Command-Funktionen also nicht so generisch halten, dass sie ohne Abfrage im Executor funktionieren. Zumindest nicht ohne Übergabe weiterer Informationen wie Typ-IDs.
Sonst fällt mir auch nur gerade eine Get-Methode im Command ein, womöglich mit einem
enum. Dürfte unter Umständen ein wenig schneller sein als derdynamic_cast, ist dafür aber hässlich. Mir scheint, dein Design ist mal eine berechtigte Anwendung vondynamic_cast- solls ja auch geben.
-
Nexus schrieb:
Sonst fällt mir auch nur gerade eine Get-Methode im Command ein, womöglich mit einem
enum. Dürfte unter Umständen ein wenig schneller sein...Ich bin mir zwar nicht sicher, aber ich denke, das ist fast das Gleiche. Hinter der vtable steckt ja im Prinzip auch nur eine Art
enum+Sprungtabelle. Wenn derdynamic_castfehlschlägt, geht das Programm sinnbildlich in dendefault-Zweig eines switch-case-Blocks und liefert dennullptrzurück.
-
"Executoren". Deshalb heissen die auch so ...
Jaja, ich weiss schon, was das command pattern ist. Hat mich nur sehr an Execution in the kingdom of nouns erinnert. Und mit "distribute" kann ich mich mehr anfreunden als mit "execute" und ob nun MessageBus, CommandBus oder nur Bus ist mir egal.
Was du versuchst ist: Bedingte Ausfuehrung abhaengig vom Typ (gemischt mit deinem command pattern). Das wird in C++ normalerweise durch virtuelle Methoden realisiert. Dann willst du auch noch andere Typen wie DrueckMichExecutor den Klassen DrueckMichCommand zuordnen, alles dynamisch (das ist der Knackpunkt). Da jedoch die Executoren (manche exekutieren bestimmt auch Leute
) selbst herausfinden muessen, auf welche Commands/Messages sie reagieren, kannst du garnicht anders als RTTI verwenden. Ob nun dein eigenes oder das von C++, ob durch typeinfo, dynamic_cast oder eine ID spielt keine Rolle.Kompliziert wird es nur, wenn mehrere Exekutoren (Henker?) auf das gleiche Ereignis reagieren koennen (auf den gleichen Kopf scharf sind), wenn auf bestimmte Ereignisse nur einmal reagiert werden soll etc. ...
PS: Sorry das ich auf Executor so rumreite, aber persoenlich mag ich den Begriff nicht sonderlich.
-
knivil schrieb:
Jaja, ich weiss schon, was das command pattern ist.
Sorry, ist mir schon klar. Ich war zugegebenermaßen etwas sarkastisch.

knivil schrieb:
Und mit "distribute" kann ich mich mehr anfreunden als mit "execute" und ob nun MessageBus, CommandBus oder nur Bus ist mir egal.
Okay, über die Begrifflichkeiten kann man tatsächlich streiten. Mal sehen, ob ich da nochmal ein wenig umbenenne.
knivil schrieb:
Was du versuchst ist: Bedingte Ausfuehrung abhaengig vom Typ[...]kannst du garnicht anders als RTTI verwenden. Ob nun dein eigenes oder das von C++, ob durch typeinfo, dynamic_cast oder eine ID spielt keine Rolle.
Darum ging es mir. Ich war mir halt nicht sicher, ob ich vielleicht doch ein Brett vor dem Kopf hatte. Aber es scheint wohl so okay zu sein.
knivil schrieb:
Kompliziert wird es nur, wenn mehrere Exekutoren (Henker?) auf das gleiche Ereignis reagieren koennen (auf den gleichen Kopf), wenn auf bestimmte Ereignisse nur einmal reagiert werden soll etc. ...
Dafür gibt es schon spezialiserte Versionen von
KommandoundHenker
. Die kümmern sich um Broadcast, Multicast und "PtP"-Kommandos.knivil schrieb:
PS: Sorry das ich auf Executor so rumreite, aber persoenlich mag ich den Begriff nicht sonderlich.
Wie gesagt, der Methodenname ist vielleicht wirklich nicht an allen Stellen optimal.
Lese auch das "PS" im vorletzten Post.

-
Tatsächlich steckt dahinter ...
Ich weiss, ab einem bestimmten Punkt wird es kompliziert (fuer mich zumindestens).

-
knivil schrieb:
double dispatch (in diesem fall IMO total unpassend)
Warum ist double dispatch unpassend?
OK, "IMO" ist falsch, da es weniger eine Meinung, sondern eher eine Vermutung von mir ist.
Ich vermute nämlich, dass das ein System werden soll, wo man "Commands" und "Executors" hinzufügen können soll, ohne grossartig viel Code zu ändern.Bei double dispatch muss man aber entweder die "Commands" alle ändern, wenn ein neuer Executor dazukommt. Oder die Executor alle ändern, wenn ein neues Command hinzukommt.
-
hustbaer schrieb:
OK, "IMO" ist falsch, da es weniger eine Meinung sondern eine Vermutung von mir ist.
Ich vermute nämlich, dass das ein System werden soll, wo man "Commands" und "Executors" hinzufügen können soll, ohne grossartig viel Code zu ändern.Jo, genau.
-
Naja, bei Command::execute( Context ) braeuchte man keine Henker ... aeh Executor.
Aber das ist natuerlich vom uebergeordnetem Problem abhaengig.
-
knivil schrieb:
Naja, bei Command::execute( Context ) braeuchte man keine Henker ... aeh Executor.
Aber das ist natuerlich vom uebergeordnetem Problem abhaengig.Jo, aber das widerspricht, wie bereits gesagt, den Kommandomuster. Kommandos führen nichts aus, sondern werden ausgeführt.
-
Das sind doch nur Begrifflichkeiten aus der "realen" Welt. Dort muss es immer jemanden geben, der den Befehl ausfuehrt. Aber meist ist es nicht entscheidend, wer den Befehl ausfuehrt. Auch wuerde ich die Serialisierung, Deserialisierung und globale Verteilung der Nachrichten nicht von einem (extra) Executor machen lassen, sondern es quasi als built-in des Nachrichtensystems realisieren. Ein Executor kann ja prinzipiell nur Messages/Commands verarbeiten, jedoch keine neuen generieren oder in einen lokalen Buss einspeisen. Wenn er es doch koennen soll, dann waere er kein Henker mehr sondern eher ein Geraet, aus dem "Executor" wuerde ein "Device" (wobei das zu sehr an Technik angelehnt waere) werden.
aber das widerspricht, wie bereits gesagt, den Kommandomuster
Damit kann ich leben.

-
knivil schrieb:
Wenn er es doch koennen soll, dann waere er kein Henker mehr sondern eher ein Geraet, aus dem "Executor" wuerde ein "Device" (wobei das zu sehr an Technik angelehnt waere) werden.
Wieso das? Wer sagt denn, dass ein Ausführer nicht selbst wieder Kommandos erzeugen kann? Mal davon abgesehen, generiert der "RemoteExecutor" auch kein neues Kommando. Er verarbeitet Kommandos. Die Verarbeitung besteht in der Serialisierung und dem anschließenden Verschicken von Daten.
Weiterhin soll das "Framework" nichts davon wissen, ob es ein Netzwerk gibt, oder nicht.
Ebenso wären lokale Verbindungen über Pipes/Shared Memory denkbar. Oder auch andere Verbindungsarten. Oder auch mehrere davon parallel.
Wenn ich das alles hart ins Messagesystem rein codiere, wird das Teil immer dicker und schwerer wartbar. Wenn das Versenden aber lediglich über einen weiteren Executor geregelt wird, bleibt das ganze System sehr flexibel.
Auf der Gegenseite ist es natürlich auch kein Executor, der in den lokalen Bus einspeist, sondern eine Fabrik, die wieder konkrete Kommandos aus serialisierten Kommandos erzeugt. Als Auslöser dient hier das Ereignis des Empfangs.Inzwischen bin ich doch recht überzeugt von der Lösung.

-
Wer sagt denn, dass ein Ausführer nicht selbst wieder Kommandos erzeugen kann?
Weil ein Henker normalerweise keine neuen Verurteielten produziert (dann waere er auch Richter und Klaeger).

Wenn ich das alles hart ins Messagesystem rein codiere, wird das Teil immer dicker und schwerer wartbar.
Von hart reinkodieren sagt niemand was. Ich meine eher eine nette Schichtenarchitektur. Ganz unter: Sachen wie Pipes, Netzwerk. Mitte: Buss. Oben Geraete mit Messages.
Auf der Gegenseite ist es natürlich auch kein Executor, der in den lokalen Bus einspeist, sondern eine Fabrik, die wieder konkrete Kommandos aus serialisierten Kommandos erzeugt. Als Auslöser dient hier das Ereignis des Empfangs.
Willkommen in der Industrialisierung!
Inzwischen bin ich doch recht überzeugt von der Lösung.
Ja, sie hat schon was. Ich bin auch garnicht so abgeneigt, wie es auf den ersten Blick scheinen mag.