Fabrikmethode -- Factory Method

Design Patterns, Erklärungen zu Algorithmen, Optimierung, Softwarearchitektur
Forumsregeln
Wenn das Problem mit einer Programmiersprache direkt zusammenhängt, bitte HIER posten.
Antworten
smurfer
Establishment
Beiträge: 195
Registriert: 25.02.2002, 14:55

Fabrikmethode -- Factory Method

Beitrag von smurfer »

Hallo zusammen,

ich habe ein paar Fragen bzgl. des Einsatzes einer Factory. Wenn ich es richtig verstanden habe, ist eine der gewollten Eigenschaften einer Factory, die spezielle Implementierung einer abgeleiteten Klasse zu verstecken und lediglich das Interface mit beispielsweise einer Enumeration zu nutzen, sinngemäß:
IBase* pBase = Factory::create(INHERITED_TYPE);

Nehmen wir das übliche Beispiel eines Interfaces Shape von dem z.B. Circle und Rectangle abgeleitet sind, erhielte ich beim Erzeugen durch die Factory beispielsweise immer einen Shape-Pointer, hinter dem intern ein Circle oder Rectangle steht, z.B.:
IShape* pShape = Factory::create(SHAPE_TYPE_CIRCLE);

Was bedeutet es nun, wenn ich den Radius des Kreises oder die Eckpunkte des Rechteckes festlegen möchte? Sinngemäß
pShape->setRadius(r);
  • Vorheriges Abfragen oder Vorwissen über den Typ und Überführung mittels static_cast (oder dynamic_cast, wer mag)
  • Spezielle Factory-Methoden wie createCircle oder createRectangle -- widerspricht zumindest o.g. Kapselung und stellt den Sinn der Factory für mich in Frage (würde sich vielleicht noch für einen dahinter liegenden Memory-Pool rechtfertigen lassen)
  • Alle relevanten Methoden der abgeleiteten Klassen im Interface, im Notfall leer implementiert -- auch eher unschön
  • Keine hardcodierte Konfiguration z.B. des Radius erlauben und immer ein Laden aus Datei / Skript o.Ä. hinter die Factory legen -- verhindert zumindest das Ändern zur Laufzeit aus dem Code heraus und daher aus meiner Sicht auch eher unpraktikabel
Gibt es hier ein Best-Practice oder eine andere, elegante Option, die ich bislang nicht sehe?

Vielen Dank und beste Grüße
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: Fabrikmethode -- Factory Method

Beitrag von Alexander Kornrumpf »

Ich lehne mich mal aus dem Fenster:

"Design Patterns" (GoF) ist von Leuten, die wissen was sie tun, für Leute, die wissen was sie tun, geschrieben. Da sind dann zwar so Beispiele mit Shape und Rectangle und Circle drin (ich weiß nicht ob dieses spezielle Beispiel aus "Design Pattern" ist, aber die Beispiele darin sind zumindest alle von ähnlichem Typ), aber ich glaube man muss das als "wir nehmen jetzt dieses simpelst Beispiel um zu illustrieren was wir meinen, aber wir wissen alle welche Beispiele wirklich gemeint sind *zwinker-zwinker*" lesen. Die Sekundärliteratur zu Design Patterns ist dagegen häufig von Leuten geschrieben, die, wie soll ich sagen, nicht zur Zielgruppe von "Design Patterns" gehören und da sind solche Subtilitäten dann verloren gegangen. Kurz gesagt, es ist einfach kein besonders gutes Beispiel.

Und letztlich ist das Factory Pattern dann einfach was was man sich merken kann, für den Tag an dem man mal die Implementierung/Objekterzeugung für den Aufrufer wegabstrahieren will und wenn dieser Tag kommt, dann wird man ihn schon erkennen. Sinngemäß gilt das für alle Pattern.

Das andere ist, dass die "klassischen Pattern" alle kein Hexenwerk sind, sondern Sachen auf die man mit ein bisschen Nachdenken selbst kommen kann. Das Ziel von "Design Patterns" war nicht, festzulegen, wie wir jetzt in alle Ewigkeit vorzugehen haben, sondern zu katalogisieren was wir wissen, wohlgemerkt zu einer Zeit, als Objektorientierung nicht unbedingt der Standard der Softwaremodellierung war [citation needed]. Der Siegeszug, den Patterns in der Folge unter ganz anderen Vorzeichen in der Java Community angetreten haben sagt vielleicht mehr über Java und dessen spezielles (wenn auch zugegeben sehr großes) Einsatzgebiet aus, als über Pattern.

Oder noch anders gesagt: Nur weil Martin Fowler eine Consultingkarriere auf etwas aufgebaut hat, muss dieses etwas deshalb noch lange keine tiefere Wahrheit oder Wertigkeit enthalten. Wahrscheinlicher ist, dass Fowler ein guter Geschäftsmann ist.
smurfer
Establishment
Beiträge: 195
Registriert: 25.02.2002, 14:55

Re: Fabrikmethode -- Factory Method

Beitrag von smurfer »

Hallo Alexander,

danke für die Antwort, im Grundsatz kann ich das von dir Geschriebene klar nachvollziehen. Wie du schreibst, ist es alles kein Hexenwerk, ich habe auch schon in einigen Situationen gemerkt, dass das, was ich mir dort zurecht programmiert habe, im Grunde einem Design Pattern XY entspricht.

Grundsätzlich möchte ich auch nicht Design Pattern um der Design Pattern willen nutzen und dogmatisch meinen Code in solche zwingen. Auch wenn das Beispiel ein nicht optimales für eine Factory ist, frage ich mich unabhängig von irgendwelchen Patterns, welche Lösung die geschickteste ist.

Tendieren würde ich zum zweiten Punkt, dann habe ich zumindest die Möglichkeit, eine Art von Memory-Management im Hintergrund zu kapseln.

Edit: Abseits vom Shape-Beispiel habe ich letztlich Daten (phys. Objekte, grafische Objekte, Partikel, Emitter,...) und einen entsprechenden Datenmanager (oder von mir aus mehrere), der sich um Erzeugen und Verwalten ersterer kümmert und so von der Programmlogik trennt.
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: Fabrikmethode -- Factory Method

Beitrag von Alexander Kornrumpf »

Was ich vielleicht nicht deutlich genug gemacht hatte: Wenn ich das in der realen Welt mal angewendet habe, dann war die Situation immer so, dass das Interface "von sich aus" vollständig war, und es wirklich darum ging, Objekterzeugung zu kapseln. Das Problem, das du aufwirfst hat sich dadurch nicht gestellt. Das Problem an dem Beispiel ist, dass einem sofort Fälle einfallen in denen Rectangle und Circle nicht dasselbe Interface haben. Das hat aber mit dem Pattern nichts zu tun.
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: Fabrikmethode -- Factory Method

Beitrag von Alexander Kornrumpf »

smurfer hat geschrieben: Tendieren würde ich zum zweiten Punkt, dann habe ich zumindest die Möglichkeit, eine Art von Memory-Management im Hintergrund zu kapseln.

Edit: Abseits vom Shape-Beispiel habe ich letztlich Daten (phys. Objekte, grafische Objekte, Partikel, Emitter,...) und einen entsprechenden Datenmanager (oder von mir aus mehrere), der sich um Erzeugen und Verwalten ersterer kümmert und so von der Programmlogik trennt.
Eine Sache, die ich mal in einem Vortrag gehört hab, die man nicht glauben muss, die mir aber sofort eingeleuchtet hat ist "du wirst es nicht brauchen!" Da geht es, auf das Beispiel angewendet, darum, dass du dir jetzt Arbeit machst um die Möglichkeit eines Memory-Managements offen zu halten, die du dann nie brauchen wirst.

Bezüglich des EDITs würde ich mal ausfühlich darübe nachdenken, wie größ das gemeinsame Interface der von dir genannten verschiedenen Dinge wirklich ist, und wie nützlich es ist, eins zu haben. Ich habe darauf keine endgültige Antwort, da können andere hier sicher mehr zu sagen, aber mir scheint "nun es sind alles Daten" die falsche Abstraktionsebene. Das ist sowiso trivial immer so. Die Frage ist doch wie oft es vorkommt, dass du mit den verschiedenen Daten das gleiche machst. Die Antwort scheint mir zu "nie" zu tendieren. Irre ich mich?
smurfer
Establishment
Beiträge: 195
Registriert: 25.02.2002, 14:55

Re: Fabrikmethode -- Factory Method

Beitrag von smurfer »

Wahrscheinlich hast Du recht und es ist den Aufwand nicht wert. Allerdings ist es nicht so, dass in der obersten Abstraktion alles Daten sind, das wäre mir ebenfalls viel zu sehr abstrahiert. Es war ja auch einmal Trend, wirklich alles von einer "Entität" abzuleiten, davon bin ich weit entfernt.
Es ist eher im Kleinen: Ich habe beispielsweise ein Emitter-Interface. Emitter emittieren sowohl in meinem Fall physikalisch sehr eingeschränkte Partikel als auch physikalisch vollwertige Objekte. Das hat sich bislang als äußerst praktikabel und übersichtlich erwiesen. Allerdings benötigen die vollwertigen Objekte mehr Informationen, solche, die über ein abstraktes Interface hinausgehen. Dazu gehört z.B. ein Template (nicht im C++-Sinn, sondern im Sinne als Prototyp/Beispiel) dafür, was für Objekte emittiert werden.

Ob man sich Möglichkeiten für die Zukunft offen halten sollte (wie ein Memory-Management), ist natürlich immer schwierig zu beantworten und meiner Ansicht nach die typische Balance irgendwo zwischen Quick-and-Dirty und völlig "überdesignt".
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: Fabrikmethode -- Factory Method

Beitrag von Alexander Kornrumpf »

Allerdings benötigen die vollwertigen Objekte mehr Informationen, solche, die über ein abstraktes Interface hinausgehen. Dazu gehört z.B. ein Template (nicht im C++-Sinn, sondern im Sinne als Prototyp/Beispiel) dafür, was für Objekte emittiert werden.
Wenn diese Information nur zur Erzeugungszeit gebraucht werden und danach das Interface gleich ist, wäre das doch ein prima Anwendungsfall. Wofür wird später noch ein verschiedenes Interface gebraucht? Kannst du die Situationen in denen das der Fall ist von denen in denen das nicht der Fall ist wegkapseln? Wenn letzteres nicht geht, gewinnst du mMn nichts durch die Factory.
smurfer
Establishment
Beiträge: 195
Registriert: 25.02.2002, 14:55

Re: Fabrikmethode -- Factory Method

Beitrag von smurfer »

Guter Hinweis, müsste ich mal prüfen, inwieweit ich das insgesamt entkoppeln kann.

Wie gesagt möchte ich mich auch gar nicht am Begriff Factory oder dem Pattern als solches festbeißen -- der Betreff ist im Nachhinein betrachtet vielleicht etwas unglücklich gewählt. Letzlich möchte ich die Daten außerhalb der Logik/Algorithmen verwalten und damit am Besten auch gleich dort erzeugen.
Benutzeravatar
Top-OR
Establishment
Beiträge: 330
Registriert: 02.03.2011, 16:32
Echter Name: Jens H.
Wohnort: Esslingen/Dessau
Kontaktdaten:

Re: Fabrikmethode -- Factory Method

Beitrag von Top-OR »

Danke für diesen schönen Thread!

@Alexander: ich hätte das nicht besser in Wort fassen können. Gut, dass du das so erledigt hast.
--
Verallgemeinerungen sind IMMER falsch.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Fabrikmethode -- Factory Method

Beitrag von dot »

Die Idee der Factory Method ist eigentlich, dass du jemandem eine Factory Method geben kannst und der damit Objekte zu seiner Verwendung erzeugen kann, ohne die konkrete Implementierung dieser Objekte kennen zu müssen. Dass die erzeugten Objekte dem Benutzer der Factory ein Interface liefern, das für dessen Zwecke brauchbar ist, ist dabei eigentlich implizite Voraussetzung. In deinem Beispiel ist das aber nicht der Fall, der Benutzer deiner "Factory" will Circles erzeugen, die "Factory" liefert ihm aber nur irgendwelche Shapes. Es fällt mir schwer, einzuordnen, ob dein Beispiel nun eher aus einem Missverständnis des Factory Method Pattern oder dem Versuch der Anwendung des Pattern auf ein völlig unpassendes Problem entstanden ist. Ich geb einfach mal das passendere Beispiel einer Factory Method, die dir Circles liefert, ohne dass du dabei wissen musst, ob diese Circles nun Kreise in einem SVG File oder auf dem Bildschirm oder auf einem Plotter oder auf dem Bildschirm eines, über das Internet verbundenen, zweiten Rechners oder was auch immer darstellen. Eine solche Factory Method kannst du dann an Code übergeben, der einfach allgemein irgendwie Kreise generiert und dem dabei völlig egal sein kann, wie diese Kreise am Ende ausgegeben werden. Durch Wechseln der an den Code übergebenen Factory Method kann der selbe Code seine Kreise dann in eine SVG Datei ausgeben oder direkt auf den Bildschirm oder auf einen Plotter oder auf den Bildschirm eines anderen, über das Internet verbundenen, zweiten Rechners...

Wann immer du dich in der Situation befindest, dass du an einer Stelle ein Interface hast, mit dem der Code, der mit dem entsprechenden Objekt was tun sollte, nichts anfangen kann, zeigt dies einen Designfehler an. Ein Downcast, egal ob nun in Form eines static_cast oder dynamic_cast oder per selbst gebastelte, explizite Typabfrage oder was auch immer, löst niemals das eigentliche Problem, sondern lindert nur die Symptome, bis die Krankheit an anderer Stelle wieder ausbricht, in der Regel schlimmer als beim ersten Mal. Die Frage zu deinem Beispiel sollte nicht sein "Wie mach ich es am besten, dass ich nun den Radius dieses Shape ändern kann?" sondern "Wieso hab ich hier nur einen Shape wenn ich doch eigentlich einen Circle wollte?". Die Suche nach der Antwort führt dann sehr schnell zu weiteren Fragen wie z.B. "war dieses Shape Interface wirklich eine gute Idee?", "macht es wirklich Sinn, Circle von Shape abzuleiten?", "wie sieht eigentlich die Sache mit den Ellipsen aus?"... ;)
smurfer
Establishment
Beiträge: 195
Registriert: 25.02.2002, 14:55

Re: Fabrikmethode -- Factory Method

Beitrag von smurfer »

Vielen Dank für die ausführlichen und interessanten Antworten.

Wie schon im ersten Post erwähnt ist mir der grundsätzliche Sinn einer Factory bewusst und der Titel und die Anwendung wie hier schon festgestellt unpassend. Und nein, ich möchte definitiv nicht auf Teufel komm raus eine Factory auf etwas anwenden, nur um des Design Patterns willen. Es war letztlich eine gewisse Ähnlichkeit zu einer Factory als Klasse gegeben, die mir Objekte erzeugt ohne das ich mich explizit um das Erzeugen kümmern muss. Da ich mit einem gemeinsamen Interface recht dicht an einer Factory bin, wollte ich keine Möglichkeit übersehen.

Fazit zur Begrifflichkeit: Es ist kein Memory-Pool per se und auch mit einem umgangssprachlichen Erbauer/Erzeuger/Produzenten wildere ich begrifflich wieder in fremden Design Pattern.
Fazit zur Factory: Ungeeignet
Fazit zur Kapselung der Erzeugung: Gradwanderung und fallabhängig, ob es sinnvoll ist oder nicht
Fazit zum Vorgehen: Wie erwähnt Punkt 2, gekapseltes Erzeugen der Objekte (mal völlig unabhängig davon, ob nun ein gemeinsames Interface vorliegt oder nicht, denn darum geht es mir primär nicht)
Fazit zum Architekturdesign: Für mich geeignet (Shape -> Circle war erstmal nur ein Beispiel), aber letztlich kann ich ein Design aus meiner Sicht fast immer in Frage stellen, das eine wahre Design habe ich noch nicht gesehen.

Auch wenn sehr plakativ, möchte ich natürlich mit meinen (vorläufigen) Fazits keine mögliche weitere Diskussion ersticken. Dass es wie im Beispiel beschrieben keine Anwendung für eine Factory ist und man diese auch nicht erzwingen sollte, ist denke ich klar.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Fabrikmethode -- Factory Method

Beitrag von dot »

smurfer hat geschrieben:Fazit zum Architekturdesign: Für mich geeignet (Shape -> Circle war erstmal nur ein Beispiel), aber letztlich kann ich ein Design aus meiner Sicht fast immer in Frage stellen, das eine wahre Design habe ich noch nicht gesehen.
Das eine wahre Design gibt es natürlich nicht, Design muss leben, wenn das Refactoring aufhört, ist die Software klinisch tot. Was es aber definitiv gibt, ist ganz viel schlechtes Design. Mehr kann man an dieser Stelle nicht wirklich sagen, da du uns ein dem eigentlichen Problem offenbar völlig unverwandtes Beispiel gegeben und das wirkliche Problem nicht angesprochen hast... ;)
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4254
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: Fabrikmethode -- Factory Method

Beitrag von Chromanoid »

Ein "Spruch" in diesem Zusammenhang, letztens irgendwo aufgeschnappt, den ich ganz gut finde: "prefer duplication over the wrong abstraction"
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Fabrikmethode -- Factory Method

Beitrag von Krishty »

Hmmm. Wenn man’s exakt duplizieren würde, wäre die Schnittstelle ja identisch, und es gäbe nichts zu abstrahieren. Das Problem ist doch, dass die Leute 30 Zeilen kopieren und dann in Zeile 25 aus dem AND ein OR machen.

Und dann habe ich lieber eine vollkommen falsch abstrahierte Funktion, deren Sinn ich noch selber ausarbeiten kann, als den Unterschied in den 30 scheinbar gleichen Zeilen zu suchen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
smurfer
Establishment
Beiträge: 195
Registriert: 25.02.2002, 14:55

Re: Fabrikmethode -- Factory Method

Beitrag von smurfer »

Der Spruch leuchtet ein, man könnte das "wrong" auch durch "cumbersome" oder Ähnliches ersetzen. Wie immer kommt es drauf an, Krishty kann ich auch zustimmen.

Dot, sorry, nicht falsch verstehen, ich wollte weder ein bewusst unpassendes Beispiel geben, noch ein Problem vorenthalten. Ich hielt eingangs (mittlerweile eines besseren belehrt :D ) das Beispiel für recht allgemein und damit passend. Und durch die Kommentare hat sich das Problem gelöst, bzw. meine Tendenz bestätigt.

Da es hier gerade so interessant ist, und mein Design mit Sicherheit nur besser werden kann, hier noch einmal konkreter. Hintergrundinfo: Ich befinde mich rein im Zweidimensionalen, vielleicht ist das an irgendeiner Stelle relevant. Es geht weitestgehend um eine Physikengine, Rigid-Body-Dynamics, aber zudem recht weitläufig Richtung prozedurale Erzeugung von Planetensystemen etc (das was überall gemacht wird, angefangen habe ich allerdings, bevor es boomte ;) ).

Nun zum Auslöser meiner Anfrage:
Ich habe einen WorldDataStorage, in dem vollwertig physikalische Objekte, eingeschränke physikalische Objekte wie Partikel und auch Emitter residieren. In einem Importer (lua, xml oder xfig) werden die definierten Objekte, Emitter usw. geladen und dem WorldDataStorage übergeben. Der Physikmanager, die Kollisionsabfrage und das LuaScripting können nun definiert auf den WorldDataStorage zugreifen und mit den Objekten arbeiten. Einen ähnlichen Datenverwalter gibt es für Visuals, also die grafische Repräsentation der Objekte, die nur recht lose einen Verweis auf ihrer physikalischen Referenzobjekte haben und früher oder später nicht nur im Programm, sondern auch über das Netzwerk entkoppelt werden sollen.
Ich wollte nun die Erzeugung der Objekte an den WorldDataStorage übertragen, so dass beispielsweise die Importer nur mitteilen, dass ein entsprechendes Objekt erzeugt werden soll. Der WorldDataStorage hat somit die Hoheit und Kontrollen, die Daten wie auch immer zu erstellen, zu verwalten, Pools zu nutzen usw.
Meine Emitter sind recht vielseitig, neben typischen, zeitlich emittierenden Punktquellen, können auch verschiedene Ortsverteilungen über Dichtefunktionen erzeugt werden. Dabei können sowohl einfache Partikel (Debris) oder vollwertig physikalische Objekte erzeugt werden. Genau an der Stelle fiel mir auf, dass ein Erzeugen eines Emitters im WorldDataStorage (oder meinetwegen durch seinen WorldDataStorageManager) einer Factory recht nahe kommt. Sollte ich eine solche einsetzen, erhalte ich allerdings nur ein Interface, wobei teilweise unterschiedliche Methoden benötige. Daraus resultierte mein zugegeben recht schlechtes Beispiel, da ich mich gefragt habe, ob es für solche Fälle eine elegante Factorylösung gibt, oder diese einfach nicht geeignet ist.
Aus der Diskussion stellte sich für mich nun folgendes heraus: Wenn mein WorldDataStorage die speziellen Objekte erzeugt (siehe Punkt zwei, also createObjectEmitter oder createDebrisEmitter statt nur createEmitter, habe ich nicht viel verloren, diese Factory-Abstraktion ist erstmal wenig wert). Wenn ich nun mein Design grundsätzlich dahingehend überdenke, dass ich die verschiedenen Emitter von einem gemeinsamen Interface löse statt sie in einer gemeinsamen Liste (hier momentan unordered_map) zu verwalten, sehe ich das aufgrund der vielen Gemeinsamkeiten momentan eher als Nachteil. Auf den ersten Blick spricht für mich also nichts gegen die Vererbung, auch wenn die speziellen Methoden benötigt werden, wohl aber gegen die Factory.

Ich bin der Designfrage deshalb ein wenig pauschal ausgewichen, da sie immer recht schnell gestellt wird und manchmal vom eigentlichen Thema ablenkt. An dieser Stelle bin ich durch das unkonkrete Beispiel allerdings selbst Schuld daran. Bislang hat mir das Design auch nach vielen Erweiterungen der Engine recht gute Dienste geleistet, was aber natürlich nicht heißen muss, dass es perfekt ist.

Insofern freue ich mich auf spannende Beiträge :)

Edit: Krishtys Beitrag gelesen, siehe erste Zeile und kurze Korrektur/Ergänzung: Die Emitter sind nicht mit den Objekten im oben beschriebenen WorldDataStorage, sie bedienen diesen ebenfalls. Der beschriebene Fall des Erzeugens trifft für sie jedoch auch zu. Die Vererbungsthematik trifft zudem ebenso auf Teile der Objekte und ihrer Visuals.
smurfer
Establishment
Beiträge: 195
Registriert: 25.02.2002, 14:55

Re: Fabrikmethode -- Factory Method

Beitrag von smurfer »

Wann immer du dich in der Situation befindest, dass du an einer Stelle ein Interface hast, mit dem der Code, der mit dem entsprechenden Objekt was tun sollte, nichts anfangen kann, zeigt dies einen Designfehler an. Ein Downcast, egal ob nun in Form eines static_cast oder dynamic_cast oder per selbst gebastelte, explizite Typabfrage oder was auch immer, löst niemals das eigentliche Problem, sondern lindert nur die Symptome, bis die Krankheit an anderer Stelle wieder ausbricht, in der Regel schlimmer als beim ersten Mal.
Hier möchte ich noch einmal direkt nachgefragen: Was ist denn konkret so schlimm daran? Es wird fast immer pauschal geschrieben, aber was macht es zu so einem schlechten Design, wo holt es mich wieder ein? Und, welche Alternative sollte man nutzen?

Mir ist bewusst, dass blindes Hin- und Hercasten nicht sonderlich sinnvoll ist, aber an wohl-definierten Stellen?!

Beispiel Kollisionsabfrage: Anfangs habe ich für die verschiedenen Objekttypen double-dispatching verwendet, was nach außen hin sicher recht elegant scheint und damals einfach "in" war. Es war mir aber eine zu starke Kopplung in die Objekte, ich wollte die Kollisionsabfrage in eine einzige Klasse kapseln, um sie leicht manipulieren oder ersetzen zu können. Ich kann natürlich die verschiedenen Objekte in unterschiedliche Listen* eintragen. Dies mache ich auch an den Stellen, wo das Vorgehen sich stark unterscheidet, wie bei den Partikeln und den Objekten. An anderen Stellen gibt es eine gemeinsame Liste und es wird der Typ explizit erfragt. Ich sehe kein Problem darin.
Ähnlich beim Speichern des Simulationsfortschritts. Das Emitter-Interface vereinfacht an vielen Stellen meinen Code. Beim Rausstreamen der Informationen wird die Emitterliste gestreamt. Ich könnte mehrere Listen anlegen und müsste nicht den Typ mitstreamen und beim Laden erfragen. Allerdings geschieht dies nicht während der Simulationszeit und ist von der Performance vernachlässigbar. Dafür muss ich nicht überall drei Listen pflegen.

Gibt es noch weitere Möglichkeiten, wie beispielsweise bei einer Kollisionsabfrage, verschiedene Typen zu testen, wenn nicht explizit typeigene Listen, double-dispatching oder Interfaces mit Typabfragen (sei es hausgemacht, dynamic_cast oder wie auch immer)?

* Liste als sinngemäßer Begriff, nicht zwangsweise std::list oder Ähnliche
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Fabrikmethode -- Factory Method

Beitrag von dot »

smurfer hat geschrieben:
Wann immer du dich in der Situation befindest, dass du an einer Stelle ein Interface hast, mit dem der Code, der mit dem entsprechenden Objekt was tun sollte, nichts anfangen kann, zeigt dies einen Designfehler an. Ein Downcast, egal ob nun in Form eines static_cast oder dynamic_cast oder per selbst gebastelte, explizite Typabfrage oder was auch immer, löst niemals das eigentliche Problem, sondern lindert nur die Symptome, bis die Krankheit an anderer Stelle wieder ausbricht, in der Regel schlimmer als beim ersten Mal.
Hier möchte ich noch einmal direkt nachgefragen: Was ist denn konkret so schlimm daran? Es wird fast immer pauschal geschrieben, aber was macht es zu so einem schlechten Design, wo holt es mich wieder ein?
In dem Moment, wo du einen Downcast brauchst, befindest du dich in einer Situation, in der dein Design sich selbst widerspricht. Der fragliche Code hat von seinem Aufrufer nur einen X Basisklassenzeiger bekommen, was bedeutet, dass das Interface des fraglichen Code sagt: Ich mache dieses und jenes mit jedem beliebigen X. Wenn du in der Implementierung dieses Code dann auf einmal ein konkretes Y brauchst, dann sagt uns das, dass der entsprechende Code offenbar doch nicht mit jedem beliebigen X funktioniert, sondern nur mit jenen X, die ein Y sind. Entweder hätte also das Interface bereits nach einem Y verlangen sollen, da die entsprechende Operation offenbar nur mit einem Y Sinn macht und nicht mit jedem X im Allgemeinen, oder irgendwas ist mit der Abstraktion X an sich verkehrt. Die Lösung ist in jeden Fall, das Problem im Design, das zu der entsprechenden Situation geführt hat, zu beheben und nicht – per Downcast – Code zu schreiben, der so tut, als würde er für jedes X das selbe machen, in Wirklichkeit aber, je nachdem was für ein konkretes Objekt sich hinter dem jeweiligen X befindet, etwas anderes macht...

Alternative, kurze Erklärung: Du verletzt in dem Moment, wo du einen Downcast machst, zwangsweise automatisch das Liskovsche Substitutionsprinzip...
smurfer hat geschrieben:Und, welche Alternative sollte man nutzen?
Dein konkreter Fall klingt nach einem Paradebeispiel für Double Dispatch... ;)
smurfer hat geschrieben:Mir ist bewusst, dass blindes Hin- und Hercasten nicht sonderlich sinnvoll ist, aber an wohl-definierten Stellen?!
Es geht hier nicht um Casts im Allgemeinen, sondern konkret um Downcasts (d.h. Cast von Basis runter auf abgeleitete Klasse). Ich bin absolut kein Fan von Absolutismen ;). Aber wenn es einen Fall gibt, wo man wirklich mal eine absolute Aussage machen kann, dann ist es: Ein Downcast ist immer Symptom eines Designfehlers. In all meinen Jahren ist mir noch nicht ein einziges Beispiel untergekommen, wo man imo auch nur beginnen hätte können, darüber nachzudenken, ob man vielleicht diskutieren könnte, dass ein Downcast unter gewissen Voraussetzungen nicht eventuell doch vertretbar sein könnte. Selbst nach vielmaligem, intensivem Nachdenken konnte ich bis jetzt noch nicht mal ein rein hypothetisches Beispiel für den potentiell akzeptablen Einsatz eines Downcast erfinden. Ich lasse mich natürlich immer gerne eines Besseren belehren, aber ich hab diese Diskussion schon wirklich oft geführt und der Downcast steht bei mir immer noch ganz weit oben auf der Liste der ganz groben Vergehen, gleich neben so Dingen wie dem Singleton Pattern...
smurfer hat geschrieben:Beispiel Kollisionsabfrage: Anfangs habe ich für die verschiedenen Objekttypen double-dispatching verwendet, was nach außen hin sicher recht elegant scheint und damals einfach "in" war. Es war mir aber eine zu starke Kopplung in die Objekte, ich wollte die Kollisionsabfrage in eine einzige Klasse kapseln, um sie leicht manipulieren oder ersetzen zu können.
Inwiefern war welche Kopplung zu stark? Dass die Kollisionsbestimmung zwischen verschiedenen Kombinationen verschiedener Objekttypen jeweils verschiedener Algorithmen bedarf, ist eine fundamentale Eigenschaft des Problems an sich; die Auswahl des Algorithmus ist rein prinzipiell an die Kombination der beteiligten Objekttypen gekoppelt, diese Kopplung ist ein notwendiger Umstand in jeder möglichen Lösung und kein unglücklicher Nebeneffekt eines bestimmten Lösungsansatzes. Double Dispatch liefert eine elegante Lösung für die Frage, wie man, gegeben eine beliebige Kombination von zwei Objekttypen, den passenden Algorithmus auswählen kann...
smurfer hat geschrieben:Nun zum Auslöser meiner Anfrage:
Ich habe einen WorldDataStorage, in dem vollwertig physikalische Objekte, eingeschränke physikalische Objekte wie Partikel und auch Emitter residieren.
Imo liegt da dein Problem. Sollten Emitter nicht viel eher Objekte in eine beliebige WorldDataStorage emittieren? Sollte es einer WorldDataStorage nicht absolut egal sein, was es alles für Arten von Emittern gibt oder vielleicht irgendwann mal geben wird? Abgesehen davon, würde ich mir die Frage stellen, ob es so eine gute Idee ist, einen solchen 'Emitter" wirklich ständig neue Objekte erzeugen und alte löschen zu lassen. Bei Partikelsystemen hat man in der Regel eine fixe, maximale Anzahl an Partikeln pro Emitter, die man einfach einmal alle erzeugen kann. Der Emitter führt dann nur noch das Updaten der Partikel durch. Meist will man in dem Moment, wo ein Partikel stirbt, direkt wieder ein neues emittieren. Anstatt das eine zu löschen und ein neues zu erzeugen, kann man einfach das bereits existierende resetten...
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4254
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: Fabrikmethode -- Factory Method

Beitrag von Chromanoid »

Mal verschlafen am Rande:

Als Java-Entwickler ist man ja ziemlich verseucht mit Downcast-Lösungen. Ich glaube nicht, dass das zwangsläufig ein Designfehler ist. Ich bin auch nicht davon überzeugt, dass das Liskovsche Substitutionsprinzip dadurch zwangsweise verletzt wird.

Wenn man eine (in vielen Programmiersprachen ermittelbare Eigenschaft) wie den Typ oder eben eine entsprechende Typ-ID eines Objekts als Teil des Objekts betrachtet, dann ist es keine Verletzung des Prinzips, wenn sich das Verhalten des Programms verändert, je nach dem welcher Objekt-Typ gerade in eine Methode reinflattert. Meiner Meinung nach sind dokumentierte Verträge für Schnittstellen/Klassen und ihre Methoden/Eigenschaften die "beweisbaren Eigenschaften" aus dem Liskovschen Substitutionsprinzip. Nur weil man in einer Methode auf den Subtyp eines Typs anders reagiert als auf den Typ selbst, heißt das noch lange nicht, dass diese Verträge verletzt werden. Wenn man einen Typ ableitbar bzw. die Methoden eines Typs überschreibbar macht, müssen die entsprechenden Verträge sowieso genau definiert sein. Nur wenn man dann in einem Subtyp gegen die Verträge verstößt, nur dann verstößt man auch gegen das Liskovsche Substitutionsprinzip...

Cool finde ich in diesem Zusammenhang das hier: http://ceylon-lang.org/blog/2012/01/25/enumerated-types / http://ceylon-lang.org/documentation/1.2/tour/types/
smurfer
Establishment
Beiträge: 195
Registriert: 25.02.2002, 14:55

Re: Fabrikmethode -- Factory Method

Beitrag von smurfer »

Chromanoid, sehe ich ähnlich, wenn ich es richtig verstanden habe, siehe meine Antwort im Folgenden.
dot hat geschrieben:In dem Moment, wo du einen Downcast brauchst, befindest du dich in einer Situation, in der dein Design sich selbst widerspricht. Der fragliche Code hat von seinem Aufrufer nur einen X Basisklassenzeiger bekommen, was bedeutet, dass das Interface des fraglichen Code sagt: Ich mache dieses und jenes mit jedem beliebigen X. Wenn du in der Implementierung dieses Code dann auf einmal ein konkretes Y brauchst, dann sagt uns das, dass der entsprechende Code offenbar doch nicht mit jedem beliebigen X funktioniert, sondern nur mit jenen X, die ein Y sind. Entweder hätte also das Interface bereits nach einem Y verlangen sollen, da die entsprechende Operation offenbar nur mit einem Y Sinn macht und nicht mit jedem X im Allgemeinen, oder irgendwas ist mit der Abstraktion X an sich verkehrt. Die Lösung ist in jeden Fall, das Problem im Design, das zu der entsprechenden Situation geführt hat, zu beheben und nicht – per Downcast – Code zu schreiben, der so tut, als würde er für jedes X das selbe machen, in Wirklichkeit aber, je nachdem was für ein konkretes Objekt sich hinter dem jeweiligen X befindet, etwas anderes macht...
Das ist die Art von Antwort, die ich meinte: Das Design ist schlecht, weil ein solches Design schlecht ist. Werd doch mal konkreter :)

Im Prinzip habe ich eine Kollisionsmethode, die nur X benötigt und nach außen hin mit jedem X funktioniert. Die Information welches Y das X ist, ist auch vorhanden, nicht in Form der sprachtechnischen Ausprägung als Basisklasse, sondern gewissermaßen als Member, die jedes X besitzt. Ich denke das geht in die Richtung, die Chromanoid angesprochen hatte.
dot hat geschrieben: Dein konkreter Fall klingt nach einem Paradebeispiel für Double Dispatch... ;)
dot hat geschrieben:Es geht hier nicht um Casts im Allgemeinen, sondern konkret um Downcasts (d.h. Cast von Basis runter auf abgeleitete Klasse). Ich bin absolut kein Fan von Absolutismen ;). Aber wenn es einen Fall gibt, wo man wirklich mal eine absolute Aussage machen kann, dann ist es: Ein Downcast ist immer Symptom eines Designfehlers. In all meinen Jahren ist mir noch nicht ein einziges Beispiel untergekommen, wo man imo auch nur beginnen hätte können, darüber nachzudenken, ob man vielleicht diskutieren könnte, dass ein Downcast unter gewissen Voraussetzungen nicht eventuell doch vertretbar sein könnte. Selbst nach vielmaligem, intensivem Nachdenken konnte ich bis jetzt noch nicht mal ein rein hypothetisches Beispiel für den potentiell akzeptablen Einsatz eines Downcast erfinden. Ich lasse mich natürlich immer gerne eines Besseren belehren, aber ich hab diese Diskussion schon wirklich oft geführt und der Downcast steht bei mir immer noch ganz weit oben auf der Liste der ganz groben Vergehen, gleich neben so Dingen wie dem Singleton Pattern...
Siehe oben, werd doch mal konkret :) . Was ist am obigen Beispiel der Kollisionsabfrage so schlimm, welche Fallstricke erwarten mich? Ich habe eine definierte Stelle an der ich weiß, dass Objekte ankommen, die auf Kollision geprüft werden und alle ihren Typ mit sich bringen. Nochmal darauf speziell:
dot hat geschrieben:Selbst nach vielmaligem, intensivem Nachdenken konnte ich bis jetzt noch nicht mal ein rein hypothetisches Beispiel für den potentiell akzeptablen Einsatz eines Downcast erfinden.
Deine Kernaussage es sei grundsätzlich schlecht, weil es schlecht ist, klingt für mich viel hypothetischer und diskutiert habe ich auch schon oft. ;)

Zur Kopplung:
dot hat geschrieben: Inwiefern war welche Kopplung zu stark? Dass die Kollisionsbestimmung zwischen verschiedenen Kombinationen verschiedener Objekttypen jeweils verschiedener Algorithmen bedarf, ist eine fundamentale Eigenschaft des Problems an sich; die Auswahl des Algorithmus ist rein prinzipiell an die Kombination der beteiligten Objekttypen gekoppelt, diese Kopplung ist ein notwendiger Umstand in jeder möglichen Lösung und kein unglücklicher Nebeneffekt eines bestimmten Lösungsansatzes. Double Dispatch liefert eine elegante Lösung für die Frage, wie man, gegeben eine beliebige Kombination von zwei Objekttypen, den passenden Algorithmus auswählen kann...
Diese Kopplung ist natürlich gegeben, aber das hat doch nichts damit zu tun, an welcher Stelle des Codes (örtlich gesehen) ich auf diese eingehe.

Angenommen ich möchte grundlegende Dinge an der Kollisonsabfrage ändern(1), neue Objekte hinzufügen(2) oder grundlegend verschiedene Kollisionsvarianten vorhalten(3), dann bedeutet das für die jeweilige Vorgehensweise:
Double Dispatch
  • Ermitteln welche Objekte (Klassen -> Dateien) betroffen sind und Code in den jeweiligen Objekten ändern (1)
  • Neue Kollisionsmethoden an einer Stelle für die neue Klasse erstellen und im Minimalfall nur den Rückruf in den anderen Klassen (quasi triple dispatch ;) ), ansonsten in allen beteiligten Klassen (2)
  • Varianten in den Objekten vorhalten oder Varianten der Objekte vorhalten (3)
Kapselung mit Downcast
  • Sämtlichen Code innerhalb des Kollisionsmanagers ändern (1)
  • Neue Kollisionsmethoden an einer Stelle im Kollisionsmanager erstellen (2)
  • Varianten der Kollisionsmanager vorhalten (3)
Auch ich finde Double-Dispatch elegant und habe es auch nicht grundlos anfangs implementiert. Aus rein praktischen Gründen empfand ich es aber zu verstreut im Code und wollte mich in allen drei oben genannten Fällen nur um eine Klasse/Datei kümmern. Ich finde Double-Dispatch mit Sicherheit nicht verkehrt, es ist eine persönliche Vorliebe gewesen, die gezeigte Alternative zu wählen. Was mir immer noch fehlt, ist ein wirklich praktischer Grund, was mir dabei früher oder später so sehr um die Ohren fliegt, dass es das Design schlecht macht.
dot hat geschrieben:Imo liegt da dein Problem. Sollten Emitter nicht viel eher Objekte in eine beliebige WorldDataStorage emittieren? Sollte es einer WorldDataStorage nicht absolut egal sein, was es alles für Arten von Emittern gibt oder vielleicht irgendwann mal geben wird? Abgesehen davon, würde ich mir die Frage stellen, ob es so eine gute Idee ist, einen solchen 'Emitter" wirklich ständig neue Objekte erzeugen und alte löschen zu lassen. Bei Partikelsystemen hat man in der Regel eine fixe, maximale Anzahl an Partikeln pro Emitter, die man einfach einmal alle erzeugen kann. Der Emitter führt dann nur noch das Updaten der Partikel durch. Meist will man in dem Moment, wo ein Partikel stirbt, direkt wieder ein neues emittieren. Anstatt das eine zu löschen und ein neues zu erzeugen, kann man einfach das bereits existierende resetten...
Ja, mein Fehler, siehe Edit in dem entsprechenden Post, wahrscheinlich hast Du ihn nicht mehr rechtzeitig gelesen. Die Emitter sind in einem eigenen "Storage" und emittieren in den WorldDataStorage. Partikel als solche sind in einem Ringpuffer und dieser hat eine fixe Größe. Es können aber auch physikalisch vollwertige Objekte erzeugt werden, die (undefiniert) länger leben, sie werden tatsächlich an den WorldDataStorage übergeben und sind dann als vollwertige Objekte in seiner Obhut. Und genau weil ich nicht ständig neue Objekte (im Speicher) erzeugen möchte, sollte der WorldDataStorage das Erzeugen der Objekte im Speicher übernehmen, wobei ich wieder beim Grund meines ursprünglichen Anliegens angekommen bin.

Edit: Copy-Paste-Fehler bei Punkt (3) der Kapselung-mit-Downcast-Liste korrigiert.
Zuletzt geändert von smurfer am 07.02.2016, 11:47, insgesamt 1-mal geändert.
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4254
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: Fabrikmethode -- Factory Method

Beitrag von Chromanoid »

Ich bin hier voll auf Deiner Seite. Ich glaube sogar, dass wenn eine Sprache keine oder nur "widerwillig" Mittel zur Ermittlung des Objekt-Typs bereitstellt, das automatisch zu einer starken Behinderung des Zusammenspiels von Bibliotheken aus unterschiedlicher Hand führt. Und ich habe auch tatsächlich das Gefühl, dass sich die Entwicklungen von Bibliotheken z.B. in C++ immer sehr stark um sich selbst drehen.

Ich glaube genau Dein Usecase ist ein Grund für das Einführen von Reflection und ähnlicher Ansätze. Möchte man zum Beispiel einen Editor für ein Objektmodell entwerfen, ist man bei Sprachen ohne Reflection sehr darauf angewiesen, dass das Objektmodell diese Möglichkeit (und am besten auch noch viele andere) antizipiert. Die Komplexität, die bei dieser Antizipation in das Objektmodell wandern würde, ist bei vielen Sprachen einfach verallgemeinert worden und in eine Relfection-API gewandert.
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: Fabrikmethode -- Factory Method

Beitrag von Alexander Kornrumpf »

smurfer hat geschrieben: ... WorldDataStorage ... Physikmanager ... Einen ähnlichen Datenverwalter gibt es für Visuals ... WorldDataStorageManager
Vielleicht bin ich naiv, aber ich denke, wenn du die o. g. Parasitenklassen wegwirfst werden sich deine Designprobleme wahrscheinlich von alleine lösen.
smurfer
Establishment
Beiträge: 195
Registriert: 25.02.2002, 14:55

Re: Fabrikmethode -- Factory Method

Beitrag von smurfer »

Hm, das finde ich nicht naiv, sondern eher ein wenig provokant und auch pauschal formuliert -- die Begriffe hast Du schon sehr plakativ rausgezogen. Nenn es nicht Manager und belass es in irgendeiner einzelnen Methode/Funktion und schon ist es nur noch eine Schleife, die durchlaufen wird -- irgendwer muss es machen. Und die Daten an bestimmten Stellen von der Logik zu trennen, empfinde ich auch nicht grundsätzlich als parasitär.

Abgesehen davon mal ganz direkt: Welche Designprobleme? Ich sehe ja gar kein großartiges Designproblem, das ich loswerden müsste. Ich wollte nur verschiedene Möglichkeiten ausloten und schauen, ob ich etwas übersehen habe.
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Fabrikmethode -- Factory Method

Beitrag von Spiele Programmierer »

Und die Daten an bestimmten Stellen von der Logik zu trennen
Das widerspricht meines Erachtens der Idee der Objektorientierung und Kapselung.
Schließlich soll die Datenrepräsentation und Funktionsweise in der Kapselung von außen nicht erkennbar sein.
Klassen im Sinne der Objektorientierung sind mehr als Strukturen in C.

Sowohl "WorldDataStorage" als auch einen "WorldDataStorageManager" zu haben wirkt für mich sehr seltsam.
Warum muss man den "Storage" "managen". Warum kann sie das nicht selbst tun. Wenn es eine "World" gibt, kann die doch außerdem einfach die Daten direkt besitzen. Es klingt weder so, als ob man die Klasse irgendwo anders brauchen kann, noch so, als ob sie selbst sinnvoll Aufgaben übernehmen kann.
Das ist die Art von Antwort, die ich meinte: Das Design ist schlecht, weil ein solches Design schlecht ist. Werd doch mal konkreter :)
Down Casts halte ich auch für gefährlich und zu vermeiden. Dot hat es schon schön dargelegt. Ich würde zwar nicht so weit gehen und sagen, dass man sie immer (auf eine sinnvolle Weise) vermeiden kann, aber doch sehr häufig. Das sollte man deswegen, weil der Typ die "Kompetenzen" angibt sollte, die das Objekt hat. Wenn an einer Stelle mehr "Kompetenzen" benötigt werden als eigentlich angegeben, ist das ein Problem - ein Designproblem. Ganz konkret kann das dazu führen, dass Laufzeitfehler bzw. Undefined Behaviour ausgelöst werden, die andernfalls zur Compile-Zeit aufgefallen wäre. Außerdem verschlechtert es die Verständlichkeit und Dokumenation des Codes an der Schnittstelle selbst, wenn ein Objekt gecastet werden muss, damit es wie notwendig verwendet werden kann. Das es in einem anderen Typ gedowncastet werden muss bzw. kann steht schließlich nicht im Code der Schnittstelle.

Andernfalls könnte man doch auch einfach void* verwenden? Hmm? Warum denn nicht, ist schließlich auch eine Möglichkeit, oder?
Designprobleme? Achwas - bestimmt hypothetische Bedenken. ;)
Und ich habe auch tatsächlich das Gefühl, dass sich die Entwicklungen von Bibliotheken z.B. in C++ immer sehr stark um sich selbst drehen.
Ich weiß gerade nicht, was du damit meinst.
Hört sich an, als ob in Java mittels Reflection fremde Bibliotheken automatisch erkannt und verwendet würden???
Zuletzt geändert von Spiele Programmierer am 07.02.2016, 21:01, insgesamt 1-mal geändert.
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4254
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: Fabrikmethode -- Factory Method

Beitrag von Chromanoid »

Spiele Programmierer hat geschrieben:
Und ich habe auch tatsächlich das Gefühl, dass sich die Entwicklungen von Bibliotheken z.B. in C++ immer sehr stark um sich selbst drehen.
Ich weiß gerade nicht, was du damit meinst. Hört sich an, als ob in Java werden mittels Reflection fremde Bibliotheken automatisch erkannt und verwendet würden???
Ja, so in der Art kommt das häufig vor. Insbesondere bei Serversystemen ist das sehr praktisch. Stell Dir vor, Du programmierst ein paar Klassen steckst sie in ein Modul (JAR/DLL) und schiebst das Modul in einen Application Server. Der Server lädt Deine Server-Klassen automatisch, exponiert sie, schiebt Dir per Reflection die richtige extern konfigurierte Datenbankverbindung in die Request-Behandlungsmethode usw. Genauso gibt es zahlreiche Bibliotheken, die z.B. dein POJO anhand der Eigenschaften in ein Template zur Ausgabe füllen usw. MVVM und XAML lassen grüßen. Auch Bibliotheken, die Objekte serialisieren/deserialisieren, haben dank Reflection trotz dynamischer Verlinkung ein leichtes Spiel.

Das lässt sich alles auch mit C++ erledigen, aber eben nicht so einfach und schon gar nicht, wenn man das ganze auch noch dynamisch linken will.

Eine noch viel größere Rolle spielt bei der Integrierbarkeit von Bibliotheken in Java und Co. sicher das dynamische Laden des Codes und die damit einhergehende Einfachheit Bibliotheken mit ihrem gesamten Objektmodell ohne zusätzliches Wissen zu verknüpfen. Aber trotzdem glaube ich, dass das ganze erst so richtig Fahrt aufnimmt, wenn zur Laufzeit die Datenströme aus den Bibliotheken anhand von Reflection gezielter verarbeitet werden können. Reflection und dynamisches Laden führt, wenn man nicht aufpasst, natürlich auch zu äußerst gruseligen und bedrohlichen Deserialisierungs-Exploits. Alles hat eben sein Für und Wider.

Naja aber das ist wohl alles offtopic. Ganz interessant finde ich trotzdem, dass man wahrscheinlich in den meisten Spiele-Engines Ansätze für Reflection finden kann. Sobald fremde Entwickler Funktionalität hinzufügen sollen, die nicht Teil eines großen Ganzen ist, und man die Resultate dieser Entwicklung trotzdem so verarbeiten möchte, kommt es natürlicherweise zu Implementierungen von Reflection oder Reflection-artigen Systemen. In der Unreal Engine gibt es z.B. das UObject von dem AFAIK alle Gameplay-Sachen erben, das bringt natürlich gleich auch Reflection mit. Der Editor kann so die Eigenschaften anzeigen, der Netzwerkcode die richtigen Felder in der richtigen Häufigkeit an die anderen Mitspieler übermitteln usw.
smurfer
Establishment
Beiträge: 195
Registriert: 25.02.2002, 14:55

Re: Fabrikmethode -- Factory Method

Beitrag von smurfer »

Spiele Programmierer hat geschrieben:
Und die Daten an bestimmten Stellen von der Logik zu trennen
Das widerspricht meines Erachtens der Idee der Objektorientierung und Kapselung.
Schließlich soll die Datenrepräsentation und Funktionsweise in der Kapselung von außen nicht erkennbar sein.
Vom Gefühl her sehe ich grundsätzlich keinen Widerspruch, bzw. kann mir durchaus vorstellen, an geeigneten Stellen stärker objektorientiert und an anderen modularer vorzugehen. Vielleicht passt hier auch das Wort Kapselung nicht. Allerdings möchte ich Dir auch nicht grundsätzlich widersprechen, da ich nicht genau weiß, ob ich dich richtig verstanden habe und mit auch mit den Begrifflichkeiten nicht ganz sicher bin.
Spiele Programmierer hat geschrieben:Klassen im Sinne der Objektorientierung sind mehr als Strukturen in C.
Ja, das ist wohl wahr. Siehe oben, so ganz verstehe ich nicht, worauf du mit der Anmerkung hinaus möchtest. Gebe ich dir das Gefühl, eine Klasse sei für mich nur eine C-Struktur?
Spiele Programmierer hat geschrieben:Sowohl "WorldDataStorage" als auch einen "WorldDataStorageManager" zu haben wirkt für mich sehr seltsam.
Einverstanden, den Manager braucht es nicht.
Spiele Programmierer hat geschrieben:Down Casts halte ich auch für gefährlich und zu vermeiden. Dot hat es schon schön dargelegt. Ich würde zwar nicht so weit gehen und sagen, dass man sie immer (auf eine sinnvolle Weise) vermeiden kann, aber doch sehr häufig. Das sollte man deswegen, weil der Typ die "Kompetenzen" angibt sollte, die das Objekt hat. Wenn an einer Stelle mehr "Kompetenzen" benötigt werden als eigentlich angegeben, ist das ein Problem - ein Designproblem. Ganz konkret kann das dazu führen, dass Laufzeitfehler bzw. Undefined Behaviour ausgelöst werden, die andernfalls zur Compile-Zeit aufgefallen wäre. Außerdem verschlechtert es die Verständlichkeit und Dokumenation des Codes an der Schnittstelle selbst, wenn ein Objekt gecastet werden muss, damit es wie notwendig verwendet werden kann. Das es in einem anderen Typ gedowncastet werden muss bzw. kann steht schließlich nicht im Code der Schnittstelle.
Auch das ist ja grundsätzlich nicht verkehrt, es ist gut wenn man nicht downcasten muss. Es kann zu Problemen führen wenn wahrlos eingesetzt. Aber nochmal zum konkreten Beispiel der Kollisionsabfrage. Es handelt sich um einen klar definierten, in sich geschlossenen Bereich, in dem jegliches Vorwissen gegben ist. Die "Kompetenz" ist nun nicht c++-sprachlich durch die Klasse also solche gegeben, sondern über das Interface und den mitgegebenen Typ. Wo ist das Problem?
Ob die Lesbarkeit bei double dispatch nun größer ist als bei einer einzelnen Kollisionsklasse mit wohldefinierten Downcasts wage ich zu bezweifeln.
Spiele Programmierer hat geschrieben:Andernfalls könnte man doch auch einfach void* verwenden? Hmm? Warum denn nicht, ist schließlich auch eine Möglichkeit, oder?
Designprobleme? Achwas - bestimmt hypothetische Bedenken. ;)
Warum ins Lächerliche ziehen? Ich spreche niemandem ab, dass double dispatch und Vermeiden von Downcasts ein gutes Design ist. Ich sehe nur ernsthaft in klar abgesteckten Szenarien wie den angesprochenen nicht den dogmatisch erwähnten Designfehler, wenn es mir an anderer Stelle Vorteile verschafft und die Handhabung vereinfacht.
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: Fabrikmethode -- Factory Method

Beitrag von Alexander Kornrumpf »

smurfer hat geschrieben:Hm, das finde ich nicht naiv, sondern eher ein wenig provokant und auch pauschal formuliert -- die Begriffe hast Du schon sehr plakativ rausgezogen.
Provozieren wollte ich wenn überhaupt nur dazu es mal ganz anders zu betrachten.
Nenn es nicht Manager und belass es in irgendeiner einzelnen Methode/Funktion und schon ist es nur noch eine Schleife, die durchlaufen wird -- irgendwer muss es machen. Und die Daten an bestimmten Stellen von der Logik zu trennen, empfinde ich auch nicht grundsätzlich als parasitär.
Klingt sinnvoll. Aber wieso muss innerhalb der Schleife der Typ unterschieden werden? Wenn verschiedene Typen verschieden behandelt werden wollen, täten es auch mehrere Schleifen über jeweils homogene Daten?!
Abgesehen davon mal ganz direkt: Welche Designprobleme? Ich sehe ja gar kein großartiges Designproblem, das ich loswerden müsste. Ich wollte nur verschiedene Möglichkeiten ausloten und schauen, ob ich etwas übersehen habe.
Ich bin nicht ganz sicher, worüber wir dann hier diskutieren. Wenn du Code hast, der für dich funktioniert, würde ich den nicht anfassen, bevor nicht irgendein Problem auftaucht, was das nötig machen würde.
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: Fabrikmethode -- Factory Method

Beitrag von Spiele Programmierer »

Ja, das ist wohl wahr. Siehe oben, so ganz verstehe ich nicht, worauf du mit der Anmerkung hinaus möchtest. Gebe ich dir das Gefühl, eine Klasse sei für mich nur eine C-Struktur?
Die Klasse "WorldDataStorage" schien irgendetwas zu speichern ("Storage") also Daten zu halten ohne selbst für die Verwaltung zuständig zu sein. Deshalb gab es ja den "Manager". Analog zu einer C-Struktur: Variablen hier, freie Funktionen dort.
Aber scheinbar sind wir uns ja eh einig man die "Manager"-Klasse nicht braucht.
smurfer
Establishment
Beiträge: 195
Registriert: 25.02.2002, 14:55

Re: Fabrikmethode -- Factory Method

Beitrag von smurfer »

Nur in aller Kürze:
Spiele Programmierer hat geschrieben:Die Klasse "WorldDataStorage" schien irgendetwas zu speichern ("Storage") also Daten zu halten ohne selbst für die Verwaltung zuständig zu sein. Deshalb gab es ja den "Manager". Analog zu einer C-Struktur: Variablen hier, freie Funktionen dort. Aber scheinbar sind wir uns ja eh einig man die "Manager"-Klasse nicht braucht.
Ah verstehe, dann ist für mich der Hinweis nachvollziehbar. Und ja, wie vorgeschlagen sollte WorldDataStorage Daten halten und auch verwalten.
Alexander hat geschrieben:Wenn verschiedene Typen verschieden behandelt werden wollen, täten es auch mehrere Schleifen über jeweils homogene Daten?!
Ja, genau. Es ist für mich einfach wieder ein Abwägen des Für und Widers, da sowieso ein gemeinsames Interface besteht und ich es so an manchen Stellen für mich vereinfachen kann. Wie schon weiter vorne im Thread beschrieben, trenne ich aber genauso in mehrere Schleifen auf, wenn die Unterschiede zu groß sind oder ein gemeinsames, womöglich umständliches Interface erst erstellt werden müsste. Darauf läuft es im Kern für mich auch beim Kollisionsbeispiel hinaus: ein Abwägen von elegantem Design gegen die für mich praktikablere Lösung mit dem diskutierten Risiko des vermeintlich schlechten oder als widersprüchlich empfundenen Designs.
Alexander hat geschrieben:Ich bin nicht ganz sicher, worüber wir dann hier diskutieren. Wenn du Code hast, der für dich funktioniert, würde ich den nicht anfassen, bevor nicht irgendein Problem auftaucht, was das nötig machen würde.
Keine Widerrede, der Thread ist ja letztlich in zwei Richtungen verlaufen. Zum einen die konkrete Diskussion über den Charakter von Downcasts bzgl. des Architekturdesigns, zum anderen die ursprüngliche, für mich eigentlich schon beantwortete Frage nach dem sinnvollsten Vorgehen und etwaigen weiteren Optionen, wenn ein WorldDataStorage nicht nur Daten verwalten, sondern auch erzeugen und nach außen bringen soll.
Antworten