Observer vermeiden?



  • Hallo zusammen,

    ich stehe gerade vor dem Problem entscheiden zu müssen, ob ich das Observer-Pattern anwenden soll oder nicht. Problematik:

    Es gibt eine Klasse "Auto", die durch die Klasse "Stadt" verwaltet wird (Komposition). Weiterhin gibt es eine Lua-Skriptanbindung, durch die u.A. "Auto" durchgeschleift wird. Zu allem Überdruss handelt es sich um ein Netzwerk-Programm.

    Wenn ich im Skript nun z.B. die Position des Autos verändere (direkt über die Member), möchte einerseits "Stadt" davon Wind kriegen, um evtl. Kollisionstests durchzuführen. Weiter möchte auch die Server-Klasse, die sich um die Kommunikation kümmert, davon wissen, damit sie z.B. die neue Position an die anderen Clients verteilen kann.

    Jetzt frage ich mich, ob ich das mit einem Observer machen soll oder aber die Funktionalität in separate Funktionen kapseln soll, die sich um all das kümmern (im Stile von Server::SetAutoPosition(), wo dann zunächst der Kollisionstest über "Stadt" durchgeführt, danach die Position in das "Auto"-Objekt gepresst und schließlich das Netzwerk-Paket auf den Weg geschickt wird).

    Letzteres hätte eine Menge Overhead zur Folge, beim Observer-Pattern müsste ich hingegen eine Menge Trigger bereitstellen, die sich bis zum Ursprung durchrufen (Auto:moved -> Stadt:auto_moved -> Server).

    Wie würdet ihr das machen?



  • StefanBo schrieb:

    Es gibt eine Klasse "Auto", die durch die Klasse "Stadt" verwaltet wird (Komposition).

    Eine Stadt hat ein Auto? würde ich fürs Erste für fragwürdig halten.

    Wenn ich im Skript nun z.B. die Position des Autos verändere (direkt über die Member),

    Mach sowas nie direkt über die Member, sondern über Methoden. Dann kann das Auto selber entscheiden, ob jemand (z.B. die Stadt) davon erfahren soll, was es mit der Anforderung macht usw.

    Wie würdet ihr das machen?

    Schwer zu sagen, du hast uns nur Teile der Problemstellung genannt, z.B. hat es den Anschein, als ob du das Auto, das eigentlich der Stadt gehört, durchs Netz zum Client quetschen willst. Hört sich irgendwie widersinnig an.



  • Eine Stadt hat ein Auto? würde ich fürs Erste für fragwürdig halten.

    Wieso nicht? Die Stadt hat weiterhin Häuser, Straßen usw. Gut, da die Autos mobil sind, könnte man sie außerhalb der Stadt aufheben, wobei die Stadt dann einen Verweis bekommt.

    Mach sowas nie direkt über die Member, sondern über Methoden.

    Sorry, da habe ich mich falsch ausgedrückt. Natürlich meine ich Methoden, in dem Fall simple Setter.

    z.B. hat es den Anschein, als ob du das Auto, das eigentlich der Stadt gehört, durchs Netz zum Client quetschen willst. Hört sich irgendwie widersinnig an.

    Es ist ein Server/Client-Modell. Auf dem Server wird das Auto simuliert und dessen Position an die Clients verteilt.

    Ein Lua-Skript, das auf dem Server läuft, könnte beispielsweise auto:set_position( 12, 34 ) durchführen, was direkt zum C++-Auto-Objekt geht. Kollisionstest wäre mir hier nicht einmal so wichtig (ich denke man kann davon ausgehen, dass der Benutzer das durchaus bewusst macht ;)). Allerdings muss die Server-Klasse davon unbedingt Wind kriegen, sonst weiß niemand, dass sich das Auto bewegt hat.

    Analog gilt das natürlich auch für andere Objekte, dessen Zustand für andere wichtig sind. Das Auto-Stadt-Beispiel habe ich der Einfachheit halber gewählt. Ich überlege, ob es wirklich sinnvoll ist, einige Objekte durch Observer überwachen zu lassen, oder ob es andere gute Möglichkeiten gibt, die ich nicht kenne.



  • Was ist denn Stadt? Hat das irgendwas außer der Autos, wogegen Autos fahren können?

    Dann würde es für mich mehr Sinn machen, wenn es irgendeinen unabhängigen Platz (Klasse, Namespace, was auch immer) gibt, wo die Autos rumliegen. Von dort werden alle Bewegungsschritte per Frame gemacht und für jeden Schritt eben Kollisionstest ausgeführt. Natürlich kann ein Auto auch mit Elementen der Stadt kollidieren.

    Wichtig ist, dass eine Stadt nicht dafür zuständig ist, dass sich Autos bewegen und auch nicht dafür, Kollisionen zu prüfen, das ist verwirrend.

    Wenn Du die Bewegungen dann alle abgedeckt hast, kannst Du dort, wo Autos bewegt werden, einfach danach ein Statusupdate an den Server schicken. Das Observer-Pattern macht aber imo nur Sinn, wenn Du verschiedene Zuhörer hast, die die gleichen Daten benötigen. Du würdest hier ja wahrscheinlich ein Paket nach jedem Frame zusammenstellen und das dann an den Server schicken. Ich weiß jetzt nicht, wieso man da dieses Pattern nutzen müsste...



  • Was wenn zwei Städte kollidieren?



  • Eisflamme schrieb:

    Was ist denn Stadt? Hat das irgendwas außer der Autos, wogegen Autos fahren können?

    Ja, beliebige Hindernisse (Häuser, Laternen, was auch immer; was im Detail, dürfte keine Rolle spielen).

    Dann würde es für mich mehr Sinn machen, wenn es irgendeinen unabhängigen Platz (Klasse, Namespace, was auch immer) gibt, wo die Autos rumliegen.

    Das ist außerhalb vom Auto-Stadt-Beispiel auch der Fall.

    Von dort werden alle Bewegungsschritte per Frame gemacht und für jeden Schritt eben Kollisionstest ausgeführt. Natürlich kann ein Auto auch mit Elementen der Stadt kollidieren.

    Exakt. Die Autos sind im Grunde nicht sehr intelligente Klassen. Eigentlich kapseln sie nur ihre eigenen Eigenschaften, bieten aber selbst absolut keine weitere Funktionalität (erst recht nicht welche mit ihrer Umwelt, denn davon wissen sie ja nichts).

    Wenn Du die Bewegungen dann alle abgedeckt hast, kannst Du dort, wo Autos bewegt werden, einfach danach ein Statusupdate an den Server schicken. Das Observer-Pattern macht aber imo nur Sinn, wenn Du verschiedene Zuhörer hast, die die gleichen Daten benötigen. Du würdest hier ja wahrscheinlich ein Paket nach jedem Frame zusammenstellen und das dann an den Server schicken. Ich weiß jetzt nicht, wieso man da dieses Pattern nutzen müsste...

    Sofern die Änderungen durch die verantwortlichen Klassen stattfinden, ist das eigentlich auch gar kein Thema. Das Server-Objekt (übrigens spreche ich beim Wort "Server" hier von einer übergeordnete Klasse, nicht vom Server-Host; ich glaube, das war auch recht schwammig von mir formuliert) hält bspw. die ganzen Städte und Autos und hat weiterhin Modifizierer, die darauf wirken. Da dem Server-Objekt hier bekannt ist, was passiert, braucht man auch kein Observer-Pattern.

    Das Problem hingegen ist, wenn etwas außerhalb dieser "Kontrolle" stattfindet, was bei mir ganz konkret im Falle der Skripte passiert. In einem Skript habe ich eine Referenz direkt auf das Auto, ergo auch Zugriff auf die Setter. Wenn ich hier jetzt die Position modifiziere, kriegen sonstige Objekte davon nichts mit.

    Das ist aber sehr wichtig, weil evtl. Positions-Updates an die Clients gesendet werden müssen oder von mir aus auch ein Quadtree entsprechend der neuen Position aktualisiert werden muss.

    Zusammenfassend:
    Ändert sich der Zustand des Autos durch die gegebene Logik, die das Server-Objekt selbst durchführt (bspw. durch Geschwindigkeit, also das Auto fährt), weiß das Server-Objekt auch selbst, was passiert. Das ist der Idealfall (bzw. in der Regel auch der Normalfall).

    Wird der Zustand des Autos hingegen direkt am Objekt geändert (hier im Falle eines Skripts), umgeht das jede Aufmerksamkeit verwaltender Objekte. Ich kann keinen Quadtree anpassen und auch die Clients nicht informieren, weil ich schlichtweg nicht weiß, dass da ein Skript die Position geändert hat.



  • Was wenn zwei Städte kollidieren?

    Die Städte in meinem Spiel haben keine Reifen. 😉



  • StefanBo schrieb:

    Was wenn zwei Städte kollidieren?

    Die Städte in meinem Spiel haben keine Reifen. 😉

    Hat die Erde oder ein Meteor auch nicht und trotzdem kann es zu einer Kollision kommen! 😉



  • kollidieren schrieb:

    StefanBo schrieb:

    Was wenn zwei Städte kollidieren?

    Die Städte in meinem Spiel haben keine Reifen. 😉

    Hat die Erde oder ein Meteor auch nicht und trotzdem kann es zu einer Kollision kommen! 😉

    Fliegende/schwebende Städte habe ich aber auch nicht. 😉

    So, back to topic. Anyone? 😉



  • So, habe mir mal einen Account gemacht und probiert, die Problematik mit zwei Beispielen und einem kleinen Diagramm deutlicher zu machen. Sorry schon einmal für's Doppelposten, aber ich kann die anonymen Beiträge ja nicht editieren.

    Klick mich. Ich hoffe das Englische stört nicht.



  • Okay, so gesehen find ich das Design eher mäßig. Das Lua-Script sollte mit dem interagieren, der die Kontrolle über die "Welt" hat, nicht mit der ganzen Welt. In deinem Fall wäre das der server selbst, oder eine Ebene, die zwischen Modifier und Server gesteckt wird. Dann behälst du auch die Kontrolle darüber, was getan wird und wirst vom Script nicht überascht.



  • Okay. Die Sache ist nur, dass ich es ziemlich angenehm gefunden hätte, die Objekte von überall modifizieren zu können, ohne mir über Seiteneffekte Gedanken machen zu müssen.

    Momentan nutze ich Luabind, um die C++-Klassen nach Lua zu exportieren, und das funktioniert sehr leicht und komfortabel. Wenn ich dann das Modifizieren der Daten direkt verbiete, müsste ich ein Interface nach Lua exportieren, das die Kontrolle hat, richtig?

    Weiterhin wäre es auch irgendwie ärgerlich, für jede Operation, die ein Entity betrifft, in einem Interface Methoden bereitzustellen. Bspw.:

    class Interface {
    	public:
    		bool MoveEntity( Entity& entity, const Vector3& new_position );
    		// ...
    };
    

    Aber wie es aussieht, braucht's eine zentrale Instanz, die Herr der Lage ist. Danke pumuckl. Falls sonst noch jemand eine Idee hat: Immer her damit. 😉



  • Mal Lua-Script hin oder her, wenn die Position eines Autos verändert wird, muss es seiner Stadt melden "ich habe mich bewegt". Und die Stadt muss es dann irgendwie an dan Netzwerkserver weiterreichen (da könnte man vielleicht mit Observer arbeiten, da kenne ich jetzt das Gesamtdesign zu wenig). Und ob jetzt das Auto.SetPos(...) im Lua-Script oder im Quelltext aufgerufen wird, spielt ja eigentlich keine Rolle.



  • Deswegen ja pumuckls Vorschlag ne Zwischenschicht zu basteln, über die die Veränderungen erfolgen...

    Man kann auch für jede zu verändernde Klasse eine Proxy-Klasse basteln, die Änderungen weiterreicht, aber zusätzlich dem Server Meldung tut.



  • StefanBo schrieb:

    die Objekte von überall modifizieren zu können, ohne mir über Seiteneffekte Gedanken machen zu müssen.

    Grade wenn du die Objekt von überall modifizieren lässt, hast keine Kontrolle mehr über die Modifikationen und die Seiteneffekte. Vor allem, wenn die Autos relativ "dumme" Objekte sein sollen, muss es irgendjemanden geben, der auf sie aufpasst 😉



  • Eisflamme schrieb:

    Man kann auch für jede zu verändernde Klasse eine Proxy-Klasse basteln, die Änderungen weiterreicht, aber zusätzlich dem Server Meldung tut.

    Das wäre eine Mischung aus Proxy und Observer, wenn ich das richtig sehe. Die Idee finde ich ziemlich genial.

    In diesem Falle dann z.B. "EntityProxy", der die Position eines "Entity"-Objekts modifiziert und zusätzlich einen Observer benachrichtigt. Ich finde das sehr elegant, weil man so einerseits die Daten (Entity) von den Operationen (EntityProxy) trennt und abhängige, zusätzliche Operationen möglich macht (Senden der aktualisierten Daten über's Netz).

    Edit: Wie sieht denn das implementationstechnisch aus? Ich dachte da jetzt an Klassenmethoden, die auf die Daten wirken. Oder ist das für solche Fälle eher untypisch? (möglich ist auch, dass EntityProxy eine Referenz auf Entity hält)

    pumuckl schrieb:

    Grade wenn du die Objekt von überall modifizieren lässt, hast keine Kontrolle mehr über die Modifikationen und die Seiteneffekte. Vor allem, wenn die Autos relativ "dumme" Objekte sein sollen, muss es irgendjemanden geben, der auf sie aufpasst 😉

    Da hast du völlig recht. 🙂 Einen Proxy davor zu schalten scheint mir wirklich die (bzw. eine) sinnvolle Lösung zu sein.

    Danke an alle!



  • Ich weiß nicht, ob du wirklich für jede Entität einen Proxy brauchst. Wenn das Ganze übers Netzwerk läuft, brauchst du ja so oder so ein Handle oder eine ID für deine Entities. Wenn du einen einzelnen Entity-Manager hast, kannst du ihm mitteilen, dass du eine Entität bewegen willst und die ID/das Handle mitgeben. Der Manager kümmert sich dann um die Validierung (ist es möglich/erlaubt?) und Weiterleitung des Befehls an das Entity-Objekt, und darum, die Observer zu benachrichtigen, die sich bei ihm für die ID angemeldet haben.



  • Ich hatte auch zunächst überlegt, diese Funktionalität in "Universe" unterzubringen, wo die Entities aufgehoben werden. Mir gefällt aber der Gedanke, dass die Funktionsweise davon abgekapselt ist.

    Weiterhin fällt mir das Exportieren nach Lua um ein Vielfaches einfacher, weil ich den ganzen Proxy-Kram sehr gut verstecken kann. In einem Lua-Skript sieht es weiterhin so aus, als würde man direkt mit dem Objekt zu tun haben. Sonst müsste ich quasi den Entity-Manager exportieren, was dann nicht mehr so schön nach OOP aussieht. 😉


Anmelden zum Antworten