Basisklasse für Bewegungseigenschaften – polymorph oder nicht?


  • Mod

    Nexus schrieb:

    Klassen wie PositionAttributable sehe ich in fremdem Code fast nie, damit scheine ich mehr oder weniger allein zu stehen. Doch was für Alternativen gibt es, die ohne Codeduplizierung auskommen?

    ja, weil es ein wenig 'overengineering' ist und man heutzutage eher versucht davon wegzugehen, soviele spezialisierungen zu machen und nutzt eher data driven design. das hat viele vorteile, es macht den code simpler (egal wenn man alleine arbeitet, aber bei mehreren personen..), es macht den code schneller, es gibt dir mehr flexibilitaet in den meisten faellen, da du ohne code aenderungen (also ohne eine neue klasse durch n-ableitungen zu erstellen) neue dinge erstellen kannst (z.b. aus einer xml auslesen).
    um mal dein beispiel aufzugreifen

    struct Particle
    {
      Vector2f myPosition;
      float myRotation;
      float mySkalierung;
      Vector2f myGeschwindigkeit;
      float myWinkelgeschwindigkeit;
    };
    

    der container der ein particlesystem haelt kann dann durch alle durchgehen und die werte jedes particles verrechnen. wenn du keine skalierung brauchst, ist sie halt 1.f, brauchst du keine winkelgeschwindigkeit, ist diese 0.

    Nexus schrieb:

    Nun ist mir aber die Idee gekommen, dass es manchmal nützlich sein könnte, die gegebenen Funktionen zu überschreiben. Vor allem für eine Art Scene-Node:

    class SceneNode : public PositionAttributable
    {
        public:
            virtual void SetPosition(Vector2f position)
            {
                // Passe Kindknoten an Positionsänderung an
                BOOST_FOREACH(PositionAttributable* child, myChildNodes)
                    child->Move(position - GetPosition());
    
                // Ändere Position dieses Knotens
                PositionAttributable::SetPosition(position);
            }
    };
    

    Naheliegend wäre also, die Funktionen in der Basisklasse virtuell zu machen. Doch irgendwie gefällt mir das nicht, da man dann die VTable allen abgeleiteten Klassen aufzwingt. Klingt zwar nach Premature Optimization, doch wenn Mikro-Klassen wie Partikel polymorph werden, bekomme ich ein etwas mulmiges Gefühl. Doch eine polymorphe und eine nicht-polymorphe Version anzubieten finde ich auch nicht wirklich schön.

    das kommt mir eher wie premature overdesign vor. du hast eine vector klasse, die an sich auch eine position representieren kann, du erstellst aber eine spezielle klasse die die vektor klasse kapselt, dabei reicht die nur daten weiter. irgendwie sehe ich darin keinen vorteil. ich sehe auch nicht weshalb die SceneNode unbedingt von der position erben muss, das macht vom logischen OOP design nicht wirklich sinn, ein attribute waere doch eher eine member variable und keine base class.

    Daher die Frage: Gibts für sowas ein etabliertes Design? Oder übersehe ich etwas Grundsätzliches?

    die frage ist ein wenig was du fuer ein problem loesen willst, das wird mir nicht 100% klar. es sieht nach viel engineering aus, aber das ziel ist nicht zu erkennen und ich weiss auch nicht wie du mit sowas arbeiten wollen wuerdest. was ist wenn z.b. dein ganzer code fertig ist und du gibst nun deinen 'particle editor' an jemanden der den benutzen soll, hat er ein von dir vorgegebenes set von abgeleiteten particle kompositionen, oder machst du mit template/marco magic alle moeglichen permutationen von attributes zu partikeln zur compile time oder wie ist die arbeitsweise gedacht?



  • Ja, ich muss ehrlich sagen, dass mir das momentante Design auch nicht besonders gefällt. Wegen der virtuellen Funktionen: Ich mache eine Klasse ungern polymorph, wenn ich die Polymorphie nicht nutze. Und gerade für eine Partikelklasse, die nur ein paar Attribute hat, ist virtual wirklich Overkill, weil es gar keinen Vorteil bringt.

    Mit dem Overengineering hast du völlig Recht, rapso. Ich habe in letzter Zeit ab und zu ein Design komplett über den Haufen werfen müssen, daher versuche ich hier etwas mehr im Voraus zu planen. Aber man kanns auch übertreiben... Data-Driven Design klingt jedenfalls schon mal gut, danke für den Hinweis! Ich hab das auch in der Irrlicht-Bibliothek gesehen. Nur frage ich mich, wie man da verhindern kann, dass ein Member unabsichtlich verändert wird (z.B. die bisherige Lebensdauer bei Partikeln)... Einfach in die Dokumentation schreiben?

    Vielleicht noch zur Erklärung, warum ich sowas wie PositionAttributable und nicht ein Member Position im Kopf hatte: Erstens muss man nicht jedes Mal die Zugriffsfunktionen neu schreiben und spart so Codeduplizierung, zweitens kann man positionierbare Objekte über die Basisklasse ansprechen. Ja, der Name ist blöd, sowas wie Positionable (positionierbar) wäre vielleicht besser gewesen. Aber die Grundintention ist einigermassen verständlich?



  • Ich konnte mir nichts unter data-driven-design vorstellen: http://en.wikipedia.org/wiki/Data-driven_programming . Ich kann mir immer noch nichts darunter vorstellen.

    wie man da verhindern kann, dass ein Member unabsichtlich verändert wird

    Gar nicht. "Not worth the effort — encapsulation is for code, not people" oder "Encapsulation prevents mistakes, not espionage." http://www.parashift.com/c++-faq-lite/index.html 7.6 und 7.8

    Aber die Grundintention ist einigermassen verständlich?

    Ja, aber man kommt von vererbter Implementation ab. Besseres Design ist nur das Interface zu erben. Beispiele dafuer sind Java-Interface oder Haskells type classes. Dieser Ansatz hat sich bewaehrt, ist wohl aber noch nicht so richtig in der Welt angekommen. Natuerlich kostet sowas auch, z.B. eine vtable. Auch sollte man es nicht mit den Interface uebertreiben.

    Bei deinem Partikelsystem macht dieser Ansatz wohl wenig Sinn. Aber jedes Partikel als eigenstaendiges Objekt zu modellieren wohl auch nicht.



  • knivil schrieb:

    Gar nicht. "Not worth the effort — encapsulation is for code, not people" oder "Encapsulation prevents mistakes, not espionage."

    Ich habe aber "unabsichtlich" und nicht "böswillig" hingeschrieben.

    knivil schrieb:

    Ja, aber man kommt von vererbter Implementation ab. Besseres Design ist nur das Interface zu erben.

    Ja, man ist mit statuslosen Interfaces flexibler. Aber man muss die Grundimplementierung (nämlich das Attribut zu setzen und zu erhalten) jedes Mal neu schreiben, obwohl diese meist gleich bleibt. Eine Zwischenlösung wäre vielleicht eine Default-Implementierung des Interfaces.

    knivil schrieb:

    Aber jedes Partikel als eigenstaendiges Objekt zu modellieren [macht] wohl auch nicht [Sinn].

    Warum sollte das keinen Sinn machen? Soll ich etwa jede Information einzeln abspeichern und dann je einen separaten Container für Positionen, Farben, Geschwindigkeiten, etc. haben und zueinander synchron halten? Partikel als Objekte zu behandeln ist durchaus vernünftig. Man muss ja nicht zwingend Vererbung, Polymorphie oder Data Hiding einsetzen.



  • Langsam, langsam: eine Klasse kapselt Daten und Methoden. Wenn aber nur der Partikelmanager/ -system, in dem die Partikel verwaltet werden, Position oder Geschwindigkeit setzt, dann braucht man dafuer nicht extra eine Methode in der class/struct Partikel.



  • Das Eingangsbeispiel dafür, warum das nötig wäre, die Attribut-Klasse zu überschreiben, hallte ich für falsch. In meiner Logik speichern die Kinder immer eine Position relativ zu ihrem Vater. Wenn man den Baum runterwandert, kann man ja nebenbei die Absolut-Position auf einen Stack hauen, so man sie denn braucht.



  • knivil schrieb:

    Langsam, langsam: eine Klasse kapselt Daten und Methoden. Wenn aber nur der Partikelmanager/ -system, in dem die Partikel verwaltet werden, Position oder Geschwindigkeit setzt, dann braucht man dafuer nicht extra eine Methode in der class/struct Partikel.

    Ja, das wäre ja auch der Vorschlag von rapso. Aber dadurch bleibt ein Partikel immer noch ein Objekt. Oder hast du mit "eigenständigem Objekt" ein Objekt gemeint, das seine Methoden selbst bereitstellt? Dann haben wir uns hier missverstanden.

    Decimad schrieb:

    Das Eingangsbeispiel dafür, warum das nötig wäre, die Attribut-Klasse zu überschreiben, hallte ich für falsch. In meiner Logik speichern die Kinder immer eine Position relativ zu ihrem Vater. Wenn man den Baum runterwandert, kann man ja nebenbei die Absolut-Position auf einen Stack hauen, so man sie denn braucht.

    Stimmt, das wäre um einiges sinnvoller. Aber ich bräuchte trotzdem eine Funktion, welche die absolute Position bestimmt. Das könnte man vielleicht durch eine Rekursion über Parent-Zeiger lösen. Vielen Dank für den Hinweis!



  • Was ist denn ueberhaupt die Zielsetzung?
    Wenn man beispielsweise 100.000 Partikel auf einem Haufen hat, wuerde man die natuerlich nicht als unabhaengige Objekte im Szene-Graphen halten und pro Objekt rekursiv die Transformation ermitteln...



  • Ja, das ging mir vorhin auch durch den Kopf. Ich glaube, ich versuche gerade, viel zu viel auf einmal zu erreichen.

    Ich muss das Ganze ein wenig ordnen, dann wird auch das Design klarer... :xmas1:



  • So, und jetzt auch mal zum Thema. Ich finde, wenn man diese Attributklasse hat, dann sollte sie überall das gleiche bewirken. Sie soll ja ein eindeutige Sache, nämlich das Attribut darstellen, also sozusagen eine Garantie auf ein Ding. (Hier finde ich das aber auch als Member besser, weil das Objekt ja zwar eine Position hat, aber nicht eine Position ist. Es wäre in deinem Fall also sozusagen eine bloße Abkürzung der Schreibweise, die du durch die Ableitung bezweckst.)
    Bei echten Schnittstellen hingegen (also meinetwegen pure virtual), garantiert man ja in meinen Augen eine Garantie auf eine "Fähigkeit" des konkreten Objekts. Und ich finde halt, dass deine Herangensweise die beiden Dinge irgendwie verwurschtelt... Aber ich hab selber oft genug Design-Probleme, von daher kann ich das gerade alles falsch sehen. Im Zweifel würde ich bei OpenSceneGraph abkupfern 😉



  • Decimad schrieb:

    So, und jetzt auch mal zum Thema. Ich finde, wenn man diese Attributklasse hat, dann sollte sie überall das gleiche bewirken. Sie soll ja ein eindeutige Sache, nämlich das Attribut darstellen, also sozusagen eine Garantie auf ein Ding.

    So ähnlich war auch meine Überlegung, warum ich kein Interface dafür wollte.

    Decimad schrieb:

    Hier finde ich das aber auch als Member besser, weil das Objekt ja zwar eine Position hat, aber nicht eine Position ist. Es wäre in deinem Fall also sozusagen eine bloße Abkürzung der Schreibweise, die du durch die Ableitung bezweckst.

    Das Objekt ist keine Position, richtig. Aber es ist ein positionierbares Objekt (entsprechend dem Namen der Klasse). Wenn man nur einen Member hat, muss man die Positions-Zugriffsfunktionen jedes Mal duplizieren und kann auch nicht nur auf die Positions-Eigenschaft in der Basisklasse zugreifen. Die Vererbung scheint mir nicht grundsätzlich falsch zu sein, aber ich muss irgendwie alles richtig zusammensetzen.

    Zum sonstigen Design: Genau, schau dir meinen letzten Post an 😉



  • Ansonsten, denk auch an unsere "Properties" von drüben... Mach ne Positionable-Schnittstelle für den Quellcode, in deinem Objekt ein Attribut "position_" und dann noch ein Property "Position", dann kann man als Leveldesigner die Position mit irgendwas anderem verdrahten, das Positionen generiert. 😃
    Wenn ich mich bei OpenSceneGraph recht entsinne, dann sind Transformationen (Und als solche lassen sich ja Positionen widerspiegeln) eine eigene Klasse von Objekten... Sie sind nicht Teil des Scene-Nodes, der ein renderbares Objekt darstellt.



  • Den SceneGraph verschiebe ich wahrscheinlich auf später, wenn der Rest sauber durchdacht ist. Falls dann wieder Fragen auftreten, melde ich mich in diesem Thread.

    Ich habe jetzt jedenfalls die Partikelklasse "data-driven" gemacht, also mit öffentlichen Membervariablen. Zugriff darauf ist hauptsächlich in den Emittern und Affektoren notwendig.


Anmelden zum Antworten