Die TweenEngine hat aber gar keine Ahnung davon.
Die weiß nur ich hab Attribut A und soll das in der Zeit X mit einem bestimmten Interpolations-verfahren, in einer bestimmten Zeit, einem Wert B zuweisen.
Dann braucht es maximal einen Setter, aber keinen Getter.
Klar wird die Spiellogik den Tween(z.B Alphafading/Positionsänderung/Farbe) anstoßen, aber um die Engine zu verwenden muss sich eben Getter und Setter bereitstellen, denn es gibt keine Alternative, außer die Funktionalität im Spieleobjekt selbst zu implementieren.
Es gibt im momentanen Design keine Alternative, da das Design, so wie es ist, ja von Grund auf darauf baut. Man wird also im Rahmen des gegebenen Designs keine Lösung finden können, die ohne Getter/Setter auskommt. Ich verspreche ja keine Wunderheilungen. Man müsste das Design wohl von Grund auf überarbeiten, um zu einer solchen Lösung zu kommen...
Anderes Bsp:
Ich hab ein Button, der erzeugt bei Click ein Event. Dem Event ist eine VerursacherObjekt zugeordnet (der Button.) Im Zuge der Eventbehandlung(Dispatcher) wird dem Event dynamisch ein ZielObjekt(set) zugeordnet, wenn es bisher noch nicht bekannt war(get ==null). Ein Event ist abstrakt, es kann auch durch etwas anderes entstanden sein bei dem schon ein Zielobjekt bekannt war. Angekommen am Evenhandler, der für eben dieses Event zuständig ist werden sowohl Zielobjekt als auch Verursacherobjekt benötigt (get). (Der Button(Verursacher) wird ausgeblendet und das Zielobjekt tut irgendwas).
Immer dann wenn Ein Objekt eine Kette von Handlern (Consumerchain)oder sonstwas durchläuft (die Hierarchie einer Anwendung nach oben geht) muss es evtl mit Informationen, die zuvor nicht bekannt waren, angereichert werden. Da die jeweilige Ebene aber nicht weiß, ob die Information schon vorher bekannt war muss ich eben schauen, ob das der Fall ist oder nicht. (Getter/Setter)
Ich bin mir nicht ganz sicher, ob ich das richtig verstanden hab, aber das klingt mir sehr danach, als ob wir es hier mit dem typischen Problem zu tun haben, dass in der Hitze des Moments versucht wird, dem Event zusätzlich zu seiner eigentlichen Aufgabe noch einen Teil der Aufgabe eines anderen Objektes aufzubinden, weil es gerade angenehm erscheint. Die eigentliche Aufgabe dieses Events, das du beschreibst, ist es wohl, eine bestimmte Aktion auf einem entfernten Objekt auszuführen!? Das Routing zum Zielobjekt ist dagegen
nicht Aufgabe des Events. Es ist Aufgabe des Dispatch-Systems. Das "Verursacherobjekt" ist bei der Erzeugung des Events ja bekannt, sehr wahrscheinlich erzeugt es das Event selbst. Alle für die Ausführung der Aktion benötigten Daten können dem Event also bei seiner Erzeugung bereits übergeben werden, wir brauchen weder Setter noch Getter. Und dies sind
die einzigen Daten die das Event benötigt. Und auf diese Daten muss zu keinem Zeitpunkt von außen zugegriffen werden. Alles was das Event braucht, ist eine Methode
notify(target) die am Zielort dann mit dem Zielobjekt als Parameter aufgerufen wird und die jeweilige Aktion ausführt.
Vor allem wenn es um ein verteiltes System geht, wird es irgendein Protokoll zur Übertragung des Events benötigen. Das Zielobjekt ist etwas, mit dem dieses Protkoll sich zu befassen hat, es hat im Event nichts zu suchen. Sobald du das Zielobjekt als Property in das Event packst, muss nun plötzlich jedes Event immer ein Zielobjekt haben. Das Event ist nun auf einmal abhängig von einem Implementierungsdetail des zur Verteilung von Events zufällig gerade eben verwendeten Protokolls und das Dispatch-Systems abhängig von einer konkreten Art von Event. Beide Abhängigkeiten sind völlig unnötig und hindern mich, ohne dass es einen technischen oder logischen Grund dafür gäbe, daran, mit meinem Dispatch System etwas anderes als diese eine spezielle Art von Event zu dispatchen oder mein Event durch ein anderes System mit einem anderen Protokoll, z.B. eines das für lokale Events optimiert ist, zu verteilen.
Bei dieser "Kette von Handlern" stoßen wir dann an das nächste Problem: Das Event soll nun, während es durch verschiedene Layer läuft, mit zusätzlichen Informationen "angereichert" werden. Du sagst zwar nichts über die Natur dieser Informationen, aber du sagst, dass jeder Layer testen muss ob die Information aus anderen Layern bereits "bekannt war". Mit anderen Worten: Alle deine Layer sind nun plötzlich nichtmehr unabhängig, sondern durch dieses Event abhängig voneinander, da der Code in jedem Layer alle Informationen, die durch andere Layer hinzugefügt worden sein könnten, kennen und berücksichtigen muss. Wenn sich an einem Layer weiter unten etwas ändert, müssen alle Layer darüber ebenfalls angepasst werden, um den Code konsistent zu halten. Eine bessere Lösung wäre hier vermutlich z.B. der Decorator Pattern.
Der strikte Verzicht auf Properties hat uns somit zu einem Design frei von unnötigen Abhängigkeiten zwischen Event, Dispatch Protokoll und den einzelnen Handlern geführt. Unser System ist dadurch wesentlich einfacher (keine komplizierten if-Abfragen mehr, die in allen möglichen Teilen des Systems alle möglichen Situationen behandeln müssen, die aus dem speziellen Verhalten aller möglichen anderen Teile des System resultieren können), modularer (alle Komponenten sind nun unabhängig), flexibler (Komponenten sind unabhängig => Komponenten sind austauschbar und wiederverwendbar), weniger fehleranfällig (kein Code mehr, der manuell konsistent gehalten muss), leichter lesbar (weil einfacher), leichter wartbar (weil einfacher, weniger fehleranfällig und leichter lesbar), besser erweiterbar (weil modular und flexibel) usw. geworden...
Sorry das ich zwischenfunke, aber das
Property-System wie bei C# hat definitiv sehr viele Vorzüge und spart viel Schreibarbeit.
Es spart viel Schreibarbeit, die man besser von vornherein gar nicht hätte...
Es ist in der Anwendung das Selbe in Grün, weil es wie Get- und Set-Methoden die gleichen Aufgaben hat. Und um ehrlich zu sein, geht es mir gewaltig auf den Nerv, in C++ jedes Get und Set ausführlich in den Interfaces zu deklarieren. Ob das jetzt einzelne Werte sind oder ganze Strukturen/Objekte ist vom Endeffekt egal. Vom Zugriff ist es schöner, einfacher, genauso sicher und lässt mich schneller gleiche sich wiederholende Ergebnisse erzielen. Von daher spricht viel für das System von C#.
Das Problem ist nicht das Schreiben der Get- und Set-Methoden, das eigentliche Problem ist, dass du sie überhaupt erst haben willst.
dot, weil ich dir beim Besten willen nicht mehr bei den ständigen Themenwechsel folgen kann, kannst du dich mal in einem konkreten Snippet mal ausdrücken, wie du auf Objekteigenschaften eingehst und veränderst?
Ich verwende dazu Methoden. Da ich auf klares und simples Design setze, habe ich praktisch ausschließlich den Fall, dass Code mit Objekten arbeitet und nicht mit deren Eigenschaften. Denn Code, der von Objekteigenschaften abhängig ist, ist in der Regel Code, der in eine Methode gehört...
Daten sind letztendlich auch Eigenschaften, deren Zugriff erforderlich ist (egal ob von der Schnittstelle oder der Implementation).
Wenn du in einer Klasse Daten hast, auf die von außen zugegriffen werden muss, dann ist das nichts Anderes als ein Zeichen dafür, dass diese Daten nichts in dieser Klasse verloren haben. Sehr oft liegt der Grund darin, dass man versucht,
mehr als eine Responsibility in eine Klasse zu stopfen (wie z.B. in LetsGos Beispiel mit dem Event). Dazu sei gesagt, dass ich hier rein von Klassen im Sinne von OOP spreche, Klassen als Sprachmittel an sich kommen natürlich auch in nicht-OOP Kontexten zum Einsatz, wo man dann allerdings in der Regel die entsprechenden Member einfach public machen würde.
Weil die Nummer mit dem Brush und das jede Machart und Textur eine eigene Klasse besitzen soll, kaufe ich nicht ab.
Was genau "kaufst du mir daran nicht ab"?
Würdest du auch in konkreten Stichpunkten eingehen, wie es den Arbeitsprozess einfacher macht (Logik, Sicherheit, Performance) als bekannte Verfahren der Set und Get von Properties/Methoden?
Siehe z.B. meine Ausführungen zum Beispiel von LetsGo oben.
Aber gehen wir halt das Beispiel mit den Shapes und Brushes auch noch durch:
Um beispielsweise ein blaues Rechteck zu zeichnen, hätten wir eine Schleife, die alle vom Rechteck verdeckten Pixel abläuft und diese blau anmalt.
Um ein pinkes Rechteck zu zeichnen, hätten wir eine Schleife, die alle vom Rechteck verdeckten Pixel abläuft und diese pink anmalt.
Um ein schwarz-weiß-kariertes Rechteck zu zeichnen, hätten wir eine Schleife, die alle vom Rechteck verdeckten Pixel abläuft und diese abwechselnd schwarz oder weiß anmalt.
Um ein Rechteck mit Farbverlauf zu zeichnen, hätten wir eine Schleife, die alle vom Rechteck verdeckten Pixel abläuft und diese entsprechend dem Farbverlauf anmalt.
Um ein Rechteck mit einem Portrait von Queen Elizabeth II drauf zu zeichnen, hätten wir eine Schleife, die alle vom Rechteck verdeckten Pixel abläuft und diese entsprechend einem Portrait von Queen Elizabeth II anmalt.
Um einen blauen Kreis zu zeichnen, hätten wir eine Schleife, die alle vom Kreis verdeckten Pixel abläuft und diese blau anmalt.
Um einen pinken Kreis zu zeichnen, hätten wir eine Schleife, die alle vom Kreis verdeckten Pixel abläuft und diese pink anmalt.
Um einen schwarz-weiß-karierten Kreis zu zeichnen, hätten wir eine Schleife, die alle vom Kreis verdeckten Pixel abläuft und diese abwechselnd schwarz oder weiß anmalt.
Um einen Kreis mit Farbverlauf zu zeichnen, hätten wir eine Schleife, die alle vom Kreis verdeckten Pixel abläuft und diese entsprechend dem Farbverlauf anmalt.
Um einen Kreis mit einem Portrait von Queen Elizabeth II drauf zu zeichnen, hätten wir eine Schleife, die alle vom Kreis verdeckten Pixel abläuft und diese entsprechend einem Portrait von Queen Elizabeth II anmalt.
...
Fällt uns was auf?
Beim Zeichnen einer Form, kann man zwei fundamentale Vorgänge unterscheiden:
- Bestimmen, welche Pixel angemalt werden sollen und
- bestimmen, in welcher Farbe sie angemalt werden sollen.
Ich habe ja bereits auf abstrakter Ebene argumentiert, dass Farbe keine Eigenschaft einer Form ist. Wie man sieht, spiegelt sich dies hier auch auf Implementierungsebene wider.
Sobald wir unserem Rechteck eine Farbe als Property geben, können wir nur noch einfarbige Rechtecke malen. Wenn wir ihm einen Farbverlauf als Property geben, können wir keine einfarbigen Rechtecke mehr zeichnen, sondern nur noch welche mit Farbverlauf etc. Wir könnten natürlich separate Klassen für einfarbige Rechtecke und Rechtecke mit Farbverlauf und Rechtecke mit Textur etc. machen. Das wäre aber extrem viel Codeduplication, denn bis auf das Bestimmen der Pixelfarbe wäre der Code für alle gleich. Wir könnten ihm alle möglichen Properties geben und zur Laufzeit entscheiden, welche nun genommen werden soll. Das wäre aber ineffizient und auch fehleranfällig, da das Interface widersprüchliche Konfigurationen zuließe...
Daher separieren wir diese zwei unabhängigen Aufgaben in unabhängige Objekte:
- Ein Shape repräsentiert eine geometrische Form, er bestimmt, welche Pixel angemalt werden sollen;
- ein Brush repräsentiert den virtuellen Pinsel, mit dem ein Shape gemalt werden soll, er bestimmt, wie die Pixel angemalt werden sollen...
Shapes und Brushes können unabhängig voneinander kombiniert werden. Keine Codeduplication, alles sehr sauber gelöst, die Welt wurde wieder ein Stück besser...
PS: Und nein, der Brush sollte keine Property eines Shape sein. Dadurch würde schon wieder ein Stück der soeben gewonnenen Flexibilität verloren gehen. Früher oder später wird man beispielsweise vielleicht auf die Idee kommen, dass es ab und zu doch ganz nett wäre, Shapes nicht nur anzumalen, sondern stattdessen ihren Umriss zu zeichnen, wobei wir uns mit einfachen, durchgezogenen Linien natürlich nicht zufrieden geben, sondern strichlierte und gepunktete und dicke und dünne und blaue und pinke und... Linien wollen. Dank unserem flexiblen Design kein Problem, brauchen wir nur das Konzept eines Pen einführen und unseren Shape damit malen...