Erweitertes Kommandomuster und ekliger dynamic_cast...



  • Shade Of Mine schrieb:

    Tachyon schrieb:

    Vielleicht gibt es ja eine völlig andere Herangehensweise an das Problem. Ich sehe sie blos irgendwie nicht. 😞

    statische polymorphie. die commands sind template parameter.

    oder du machst es wie die listener in java 😉
    aber da ists besser bei dynamic_cast zu bleiben.

    Templates klar. Für das "normale" Kommandomuster sind die perfekt. Aber hier wird es schwierig, vor allem im Hinblick auf den Netzwerkteil. Ich denke, ich werde bei der jetzigen Lösung bleiben.
    Bei genauer Betrachtung ist der Einsatz des dynamic_cast hier wohl dosiert, und bildet nicht etwas nach, dass man durch normale Polymorhpie erreichen ebenfalls kann.
    Danke für die Einsichten.



  • übrigens schreibt man da oft

    void SomeExcutor::execute(Command & cmd) 
    { 
        //hier kommt der Punkt der mich stoert 
        //jeder executor muss sehen, ob das Kommando fuer ihn ist 
        if(SomeCommand * c = dynamic_cast<SomeCommand*>(&cmd)) 
        { 
            //... 
        } 
    }
    

    die parallelen vererbungsbäume *Executer und *Command hab ich auch schonmal wo gesehen. damals haben die objekte von *Executer geerbt und Executer hat auch Monitor oder Watcher gehießen und es gab keine zentrale queue für alle verschiedenen *Comnmands, sondern pro Sorte nur eine.
    ich nehme an, daß du die verschiedenen queues deshalb zu einer zusammenfassen willst, um die besser übers netz schicken zu können. andererseits mußt du beim empfänger im deserialisierer eh den genauen typ rausfinden, vielleicht wäre es dort auch richtig, die richtihe queue zu befüllen. statt queue bus.



  • Virtuelle Funktionen stellen keine Alternative dar? In dem Sinne, dass du die Aktionen in der if -Abfrage zu einem Grossteil auf die Seiten der Command -Klasse und deren Derivate auslagerst?



  • Nexus schrieb:

    Virtuelle Funktionen stellen keine Alternative dar? In dem Sinne, dass du die Aktionen in der if -Abfrage zu einem Grossteil auf die Seiten der Command -Klasse und deren Derivate auslagerst?

    Naja, konkret werden muss ich ja so oder so irgendwo. Im Nachrichtenbus sind nur Basisklassen unterwegs (sowohl die Interessenten als auch die Kommandos).
    Vielleicht verstehe ich auch nicht, was Du meinst. Wäre nett, wenn Du ein konkreteres Beispiel geben könntest.



  • 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 MessageBus auch CommandBus nennen, und das execute vom CommandBus auch distribute .
    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 ) von Executor , 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 der dynamic_cast , ist dafür aber hässlich. Mir scheint, dein Design ist mal eine berechtigte Anwendung von dynamic_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 der dynamic_cast fehlschlägt, geht das Programm sinnbildlich in den default -Zweig eines switch-case-Blocks und liefert den nullptr zurü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 Kommando und Henker 😉 . 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.


Anmelden zum Antworten