Verständnisfrage
-
Vielen Dank für die Antworten. Genau auf solche Antworten hatte ich gehofft :)...
-
c0ff33.alex schrieb:
Dann plane deine Hierarchien bevor du sie implementierst damit sowas nicht vorkommt.
Sehr naiver Ansatz!
rapso schrieb:
naja, das ist die idealisierte welt von OOP programming tutorials/buechern.
-
árn[y]ék schrieb:
c0ff33.alex schrieb:
Dann plane deine Hierarchien bevor du sie implementierst damit sowas nicht vorkommt.
Sehr naiver Ansatz!
Wenn er aufhört bei mir zu funktionieren suche ich mir einen neuen .
Ich will ja nicht sagen dass OOP gut für alles ist. Aber wenn man sich dafür entscheidet, soll man halt vorausplanen.
-
Da ich zur Zeit Teilzeit an einem Casual Game arbeite, stelle ich immer häufiger fest, das ein rein Objektorientierter Ansatz irgendwie immer in einer Sackgasse endet.
Man ist sich nicht über Verantwortlichkeiten im Klaren, "Kommunikation" zwischen Objekten wird letztendlich über Delegates o.ä. Prinzipien abgefackelt und da du i.d.R. später bestimmten Klassen mehr Funktionalitäten einräumst als jemals geplant waren, endet das Ganze bei uns momentan in einer recht monolitischen Sprite Klasse und einem Rahmensystem (Screens etc.), welche untereinander per Delegates Kommunizieren.
Ich stelle immer wieder fest, das OOP toll ist, wenn man theoretische Beispiele mal schön veranschaulichen will. In der Praxis wird der Ansatz eigentlich meistens dazu benutzt, gemeinsame EIGENSCHAFTEN zu gruppieren, aber wie im Beispiel von Rapso schön zu sehen ist, ändert sich ständig der Kontext.
Du müsstest also dein OOP Modell ständig anpassen, mal abgesehen von der statischen Verhaltensweise.
In Spielen möchtest du im Idealfall dynamisch sagen können, welche Eigenschaften und Verhaltensweisen ein Objekt hat. Du willst Rotation? Füge Sie dynamisch hinzu, leite aber nicht von einem Rotationssprite ab.
Zugegeben, für die Standard World Transformationsmember hinkt der Vergleich vielleicht ein wenig, aber alles darüber kannst du statisch eigentlich kaum mehr kontrollieren.Ich kenne allerdings auch leider keine 99% Lösung, wie man diesbzgl. am besten vorgeht. In den einschlägigen Magazinen zur Spieleentwicklung scheint man aber immer mehr in Richtung dynamischer Modelle zu wandern, wie immer das im Kern aussehen mag.
-
xaoC schrieb:
Man ist sich nicht über Verantwortlichkeiten im Klaren, "Kommunikation" zwischen Objekten wird letztendlich über Delegates o.ä. Prinzipien abgefackelt und da du i.d.R. später bestimmten Klassen mehr Funktionalitäten einräumst als jemals geplant waren, endet das Ganze bei uns momentan in einer recht monolitischen Sprite Klasse und einem Rahmensystem (Screens etc.), welche untereinander per Delegates Kommunizieren.
Klingt mir so, als wär das Design kaputt und ein Refactoring dringend notwendig.
-
c0ff33.alex schrieb:
Das Beispiel mit dem Bauhof, der mal Gebäude und mal Fahrzeug ist, macht jede Hierarchie platt.
Dann plane deine Hierarchien bevor du sie implementierst damit sowas nicht vorkommt. Ich meine, wenn man weiss, dass man später von mehreren Klassen mit der gleichen Basis ableitet, soll man halt sein Design überdenken.
Es mag ja sein, dass *Mehrfach*vererbung einem Probleme bereiten kann, aber das ist kein Grund, auf OOP zu verzichten.Naiv ist, OO mit Vererbung gleichzusetzen.
Vererbung ist oft ein gutes Mittel in Bibliotheken. Bei der Modellierung von Objekten aus der realen Welt ist es eben meistens keine simple "ist ein" Beziehung mehr.
-
dot schrieb:
Klingt mir so, als wär das Design kaputt und ein Refactoring dringend notwendig.
"Kaputtes Design" ist in 99% der Fälle ein Totschlargument.
-
lolalter schrieb:
dot schrieb:
Klingt mir so, als wär das Design kaputt und ein Refactoring dringend notwendig.
"Kaputtes Design" ist in 99% der Fälle ein Totschlargument.
Na schau dir doch mal an was er schreibt:
xaoC schrieb:
Man ist sich nicht über Verantwortlichkeiten im Klaren, "Kommunikation" zwischen Objekten wird letztendlich über Delegates o.ä. Prinzipien abgefackelt und da du i.d.R. später bestimmten Klassen mehr Funktionalitäten einräumst als jemals geplant waren, endet das Ganze bei uns momentan in einer recht monolitischen Sprite Klasse und einem Rahmensystem (Screens etc.), welche untereinander per Delegates Kommunizieren.
Wenn das für dich nicht nach kaputtem Design klingt, dann kann ich auch nicht helfen...
-
dot schrieb:
Klingt mir so, als wär das Design kaputt und ein Refactoring dringend notwendig.
Es gibt kein Design. Es ist unser erstes Projekt. Natürlich gibt es Überlegungen und Ideen, wie man etwas umsetzt, basierend auf Erfahrungen, aber es ist meiner Erfahrung nach so gut wie nie Praktikabel, etwas "Durchzudesignen".
Speziell im Game-Design muss man erstmal ein Gefühl dafür entwickeln, wie man ein einfaches Framework aufzieht, wie sich Objekte unterhalten, und zwar an konkreten Anforderungen. Ich will keine Engine schreiben, die in der Theorie toll aussieht, mit der man aber praktisch nichts anfangen kann.
Die Anforderungen ergeben sich durch das Spielekonzept. Genau dieses muss so klar wie möglich sein. Dann fängst du an, dir Gedanken zu machen wie du anhand dieses Konzept ein primitives Framework aufziehen musst.
Und hier passiert nun mal die Drecksarbeit. Du stellst schnell fest, dass das Design des Frameworks sich dynamisch an Anforderungen anpassen muss und sich dadurch teils erheblich ändert. Du hast noch keine "Foundation". Du musst erst in die Fehler rennen, um sie zu verstehen. Dann kannst du dir Gedanken darüber machen, wie du dein OOP Konzept aufsetzt (oder ob überhaupt ;-)).
Anders herum programmierst du einen theoretischen, unbrauchbaren Wasserkopf ohne Praxisrelevanz.
-
xaoC schrieb:
Natürlich gibt es Überlegungen und Ideen, wie man etwas umsetzt, basierend auf Erfahrungen, aber es ist meiner Erfahrung nach so gut wie nie Praktikabel, etwas "Durchzudesignen".
Exakt, darum ist es auch so wichtig, ständig zu refactoren...
xaoC schrieb:
Speziell im Game-Design muss man erstmal ein Gefühl dafür entwickeln, wie man ein einfaches Framework aufzieht, wie sich Objekte unterhalten, und zwar an konkreten Anforderungen.
Richtig, daher fängt man ungefähr mal grob irgendwo an und passt das Design iterativ entlang des Weges an. Ein Prozess bekannt als "Refactoring".
xaoC schrieb:
Und hier passiert nun mal die Drecksarbeit. Du stellst schnell fest, dass das Design des Frameworks sich dynamisch an Anforderungen anpassen muss und sich dadurch teils erheblich ändert.
Mit anderen Worten: Du musst ständig refactoren.
xaoC schrieb:
Du musst erst in die Fehler rennen, um sie zu verstehen.
Und wenn du die Fehler verstanden hast, besserst du sie aus -> Refactoring.
-
dot schrieb:
Wenn das für dich nicht nach kaputtem Design klingt, dann kann ich auch nicht helfen...
Nochmal kurz:
Schnapp dir deinen Gamma, kritzel dir Kombinationen aus 10 Design Patterns zusammen und sei glücklich, wenn am Ende alles aufgeht.
Hab ich auch schon oft probiert. Und mindestens genau so oft verworfen.
Grundlagenforschung ohne tiefere Relevanz, eigentlich nur dann sinnvoll, wenn du abgesteckte, relativ statische Anforderungen hast. Dann kann dir so ein Entwurfsmuster weiterhelfen.Vielleicht verstehe ich dich ja falsch, aber bei dem Wort "Design" denke ich an stundenlanges Grübeln über abstrakte Anwendungsfälle, um den "perfekten" Entwurf zu basteln.
Wenn ich 5 Interfaces implementieren muss, um einem Sprite neue Funktionalität hinzuzufügen, ist das IMO der falsche Ansatz, darum geht es mir.
-
Dann verstehst du mich falsch. Unter Design versteh ich den Entwurf von Software. Das ist in meinem Augen ein sehr organischer Prozess, Design ist einer ständigen Evolution unterworfen. Stundenlanges Grübeln und malen irgendwelcher Diagramme hilft vielleicht beim wählen der Anfangswerte der Iteration, aber das eigentliche Design ist nicht statisch, sondern entwickelt sich während die Software entwickelt wird.
-
dot schrieb:
[...] -> Refactoring.
Irgendwie sehe ich da jetzt keinen Widerspruch zu meinem Geschreibsel.
Außer evtl. dass du ein klassisches, statisches OO Klassenkonzept nicht mal eben "Refactorst".
-
xaoC schrieb:
Außer evtl. dass du ein klassisches, statisches OO Klassenkonzept nicht mal eben "Refactorst".
Was genau verstehst du unter einem "klassischen, statischen OO Klassenkonzept"?
-
Ein hauptsächlich auf Vererbung basierendes Konzept, welches Eigenschaften und Verhaltensweisen in Klassen oder Strukturen presst.
Beispielsweise die grundsätzliche Trennung von grafischen Eigenschaften (Draw) und nicht grafischen Eigenschaften (Update Only) in getrennten Klassen, womöglich nochmals abgeleitet von einem abstrakten "WorldEntity" oder so ähnlich.
Was ist z.B. ein BoundingVolume? Es hat eine WorldPosition, eine Rotation u.s.w., aber eigentlich keine Zeichenfunktionalität. Später würdest du gerne mal alle Boundingvolumes zu Debug Zwecken per Draw ausgeben, aber deine Klassenhierarchie verbietet dir das. Deine Logik bzgl. BoundingVolumes baut mittlerweile auf "WorldEntity" o.ä. auf, Refactoring bedeutet also: Du hast ein grundsätzliches Problem, was sich durch den gesamten Code zieht.
Umbauen ist evtl. nicht nur zeitaufwendig, sondern auch gefährlich, weil du getesteten Code quasi wieder neu validieren musst.
In meinen Augen hört bei sowas das statische Design auf, praktikabel zu sein.
Wenn ich die Zeit hätte, würde ich versuchen, ein kleines, aber sauberes OO Konzept zu designen, mit welchem ich dynamisch auf Objektebene zur Laufzeit Eigenschaften und Verhaltensweisen definieren kann.Natürlich habe ich immer noch einen OO Entwurf, aber um meine Objekte zu erweitern. Ich presse aber keine Funktionalität in statische Klassen.
Jetzt klarer was ich meine?
-
Ja, mir ist jetzt klar was du meinst. Was du nennst sind aber eben alles Beispiele dafür, wie man OOP nicht betreiben sollte. Die Tatsache, dass man OOP falsch verstehen und falsch machen kann, bedeutet nicht, dass OOP prinzipiell fehlgeleitet ist und gemieden werden muss. Einem Boundingvolume eine "Zeichenfunktionalität" hinzuzufügen, verletzt beispielsweise eines der Grundprinzipien der OOP, nämlich das Single Responsibility Principle. Wenn deine Klassenhierarchie irgendwas "verbietet", dann passt deine momentane Abstraktion eben offenbar nichtmehr mit deinem momentanen Problem zusammen und muss dementsprechend überdacht werden. Wenn die notwendigen Änderungen sich durch den ganzen Code ziehen, dann ist das eben so. Was wäre die Alternative? Früher oder später musst du refactoren, ansonsten werden die Probleme sicher nicht weniger werden...
Wie gesagt, all diese Programmierstile ergänzen sich wunderbar. OOP ist nur ein (durchaus wichtiges) Werkzeug von vielen. Und in der OOP geht es ganz sicher nicht darum, wer mehr Design Pattern in sein Projekt zu stopfen vermag. Um das Beispiel mit dem Bounding Volume nochmal aufzugreifen: Ein Bounding Volume sollte z.B. in der Regel vermutlich überhaupt einfach nur ein normaler Werttyp sein, fernab jeglicher OOP. Das heißt nicht, dass das komplette System auf einer höheren Ebene nicht nach OOP Prinzipien designed werden darf. Und man kann OOP wunderbar verwenden, um ein System zu bauen, welches data driven ist...
-
dot schrieb:
Ja, mir ist jetzt klar was du meinst. Was du nennst sind aber eben alles Beispiele wie man OOP nicht betreiben sollte. Die Tatsache, dass man OOP falsch verstehen und falsch machen kann, bedeutet nicht, dass OOP prinzipiell fehlgeleitet ist und gemieden werden muss. Einem Boundingvolume eine "Zeichenfunktionalität" hinzuzufügen, verletzt beispielsweise eines der Grundprinzipien der OOP, nämlich das Single Responsibility Principle. Wenn deine Klassenhierarchie irgendwas "verbietet", dann passt deine momentane Abstraktion eben offenbar nichtmehr mit deinem momentanen Problem zusammen und muss dementsprechend überdacht werden. Wenn die notwendige Änderung sich durch den ganzen Code zieht, dann ist das eben so. Früher oder später musst du refactoren, ansonsten werden die Probleme sicher nicht weniger werden...
Wie gesagt, all diese Programmierstile ergänzen sich wunderbar. OOP ist nur ein (durchaus wichtiges) Werkzeug von vielen.
Korrekt.
Letztendlich braucht's Erfahrung, um eben jene Single Responsibilities zu erkennen, zu erkennen ob der Entwurf generell Sinn macht oder man sich eines anderen Ansatzes bedienen muss.Deswegen macht es IMO keinen Sinn, komplexe Designs zu entwerfen, ohne dabei konkrete Erfahrungswerte im Hinterkopf zu haben. Eben jene Erfahrungswerte sammeln wir zur Zeit. Und wir haben schon viel refactored. Naja, man wächst an seinen Fehlern.
-
Kleine Zwischenfrage, die mir beim Mitlesen so eingefallen ist: Auch wenn es natürlich vom ganzen Rest abhängt, aber würdet ihr es eventuell für sinnvoll halten, den DoD beim fahrbaren Bauhof so zu lösen, dass man von Anfang an gar nicht zwischen Gebäude und Fahrzeug unterscheidet, sondern Fähigkeiten wie Fahren, Ballern usw. als decorator an seine Objekte hängt, die dann commands verarbeiten können oder eben nicht? Welche gerade dranhängen könnte man z.B. über states regeln.
-
Ohne weitere Infos, würde ich den fahrenden Bauhof vermutlich als zwei verschiedene Einheiten modellieren, die ineinander umwandelbar sind.
-
Wie meinste das? Bei sowas fällt mir gerade nix ein, wie das mit dem Umwandeln sinnvoll ginge.
class Gebaeude{}; class Fahrzeug{}; class StehenderBauhof : public Gebaeude {}; class FahrenderBauhof : public Fahrzeug {}; void Spiel() { vector<Gebaeude*> rumsteher; vector<Fahrzeug*> herumfahrer; }
Hattest du sowas im Kopf?
class Einheit{}; class Fahrzeug{}; class BauhofState{}; class BauhofStandingState : public BauhofState{}; class BauhofDrivingState : public BauhofState{}; class Bauhof : public Einheit { BauhofState* state_; };