C # Zusammensetzung - Ich bin nicht davon überzeugt, dass ich vollständig verstehe, wie dies zu implementieren ist

8

Okay, ich habe mich in letzter Zeit intensiv mit den Klassen, der Vererbung, den Schnittstellen und der Art und Weise beschäftigt, wie sie miteinander interagieren. Während dieser Zeit entdeckte ich eine allgemeine Verachtung für die Vererbung und eine Bevorzugung von Komposition in verschiedenen Foren / Blogs / Videos. Okay, cool etwas neues zu lernen. Anhand des Beispiels auf dieser Wiki-Seite habe ich mich daran gemacht, es zu versuchen, um es besser zu verstehen ... mein Erfolg scheint so weit zu sein um mich nur mehr zu verwirren.

Mein Code, dann erkläre ich, was ich falsch finde.

MainClass.cs

%Vor%

GameObject.cs

%Vor%

Utilities.cs (würde mir darüber keine Gedanken machen - ich habe während der Arbeit etwas über Erweiterungsmethoden herausgefunden, also warf sie auch ein)

%Vor%

Okay, die Änderungen, die ich an dem Basisbeispiel vorgenommen habe, sollten geändert werden, indem immer 3 Schnittstellen verwendet wurden, die basierend auf der Klasse 2-4 verwenden. Allerdings stieß ich sofort auf Probleme, wie dies über die Komposition geschehen kann.

Ich habe zuerst verschiedene Arten von GameObject (GameObjectAI (), GameObjectEntity () usw.) für jeden Typ gemacht, aber das schien einfach zu Code-Duplizierung und allen möglichen wackeligen Problemen im Zusammenhang mit der Vererbung zu führen - ich weiß es nicht wenn ich hier auf dem richtigen Weg wäre, aber eine schlechte nicht-kompositorische Umsetzung geschaffen hätte. Aber wenn das der Fall ist, dann verstehe ich definitiv nichts über Komposition, da ich nicht sehen konnte, wie ich es machen könnte, ohne diese Probleme zu kreieren.

Dann wechselte ich zur aktuellen Lösung. Obwohl dies grob unelegant erscheint. Während es die erwartete Ausgabe mit diesem erzeugt, kann dies nicht richtig sein - es hat Klassen wie Player () oder Entity () bekommen Schnittstellen, die sie nicht verwenden, die dann! = Null-Prüfungen haben müssen, um Laufzeitausnahmen zu stoppen Sie können natürlich ihre zugeordneten Klassen aufgerufen haben.

Also ja, da ist etwas an der Komposition Ich habe das Gefühl, dass ich es jetzt nicht richtig verstehe.

Danke!

    
Mofunkles 21.06.2017, 06:57
quelle

1 Antwort

7

Zusammensetzung? Lassen Sie uns zuerst sehen, ob Vererbung das richtige Werkzeug ist. Diese Schnittstellen sind Verhalten oder Merkmale . Der einfachste Weg, mehrere Verhaltensweisen zu implementieren, besteht darin, einfach von diesen Schnittstellen zu erben. Zum Beispiel (vereinfachter Code):

%Vor%

Der größte Nachteil dabei ist, dass Sie eine Code-Duplizierung haben (hypothetische Netrual und Friend Klassen müssen die gleiche Logik von Enemy neu schreiben).

Dafür gibt es einige Workarounds , die einfachste basiert auf der Annahme, dass Sie wahrscheinlich Klassen von Objekten haben, die einige Eigenschaften teilen. Oft brauchen Sie keine Komposition, wenn Sie eine gute Vererbungshierarchie erstellen können. Zum Beispiel hat ein bewegliches Objekt auch eine Begrenzungsbox, dann kann es auf Kollisionen prüfen:

%Vor%

Jede abgeleitete Klasse überschreibt möglicherweise das in der Basisklasse definierte Standardverhalten. Ich würde versuchen, so viel wie möglich (bis zu den Sprachgrenzen) Vererbung zu verwenden, um IS-A-Beziehungen und -Zusammensetzungen zu beschreiben, um HAS-A-Beziehungen zu beschreiben. Single-Vererbung wird uns beschränken oder wird einige Code-Duplizierung verursachen. Sie können jedoch unsere erste Implementierung verwenden und den Job an ein separates Objekt delegieren. Es ist Zeit, composition einzuführen:

%Vor%

Auf diese Weise kann Code von Moveable Implementierung von IMoveable geteilt und zwischen verschiedenen konkreten Klassen wiederverwendet werden.

Manchmal ist das nicht genug. Sie können beide Techniken kombinieren:

%Vor%

Auf diese Weise können abgeleitete Klassen ihre eigene spezialisierte Implementierung dieser Verhaltensweisen bereitstellen. Zum Beispiel kann ein Geist (vorausgesetzt, dass in unserem Spiel ein Geist ein physisches Objekt ist) mit einer Wahrscheinlichkeit von 1/10 mit einer anderen Wahrscheinlichkeit kollidieren:

%Vor%

Was ist, wenn Sie in Ihrer Game Engine Kollisionen ohne physikalischen Kontakt (zum Beispiel zwischen zwei geladenen Partikeln) handhaben müssen? Schreiben Sie einfach ein Ad-hoc-Verhalten und es kann überall angewendet werden (zum Beispiel für Elektromagnetismus und Schwerkraft).

Jetzt beginnen die Dinge interessanter zu werden. Sie können ein zusammengesetztes Objekt haben, das aus mehreren Teilen besteht (z. B. ein Flugzeug, das aus seinem Körper und seinen Flügeln besteht und möglicherweise einen anderen Widerstand gegen Waffen hat). Vereinfachtes Beispiel:

%Vor%

In diesem Fall implementiert die Liste ICollidable und zwingt dann alle Teile zu diesem Verhalten. Es könnte nicht wahr sein:

%Vor%

Zusammengesetzte Objekte können dann sogar das Standardverhalten ihrer Teile überschreiben oder hinzufügen / erweitern (der Satz hat oft nicht die gleichen Eigenschaften seiner separaten Elemente).

Wir können 1.000 weitere Beispiele schreiben, die sowohl komplexer als auch einfacher sind. Im Allgemeinen würde ich vorschlagen, dass Sie Ihre Architektur nicht überkomplex machen, es sei denn, Sie benötigen sie wirklich (besonders für Spiele ... Abstraktionen haben einen Preis in der Leistung). Sie haben es richtig gemacht, Ihre Architektur mit einem Test zu validieren, IMO ist der wichtigste Teil von TDD : Wenn Sie Ihre Tests schreiben, sehen Sie, dass Code komplexer ist als er sein sollte, dann müssen Sie Ihre Architektur ändern; Wenn Sie zuerst den Test schreiben und die Architektur folgt, wird es mehr domänenorientiert und einfach zu verstehen sein. OK, das ist der ideale Fall ... unsere bevorzugte Sprache zwingt uns manchmal, eine Lösung anstelle einer anderen zu wählen (zum Beispiel haben wir in C # keine Mehrfachvererbung ...)

Ich denke (aber das ist nur meine Meinung), dass Sie nicht über Komposition lernen sollten, ohne einen Anwendungsfall, wo es wirklich benötigt wird. Komposition und Vererbung sind nur Werkzeuge (wie Muster BTW), die Sie zur Modellierung Ihrer Domain auswählen, und Sie müssen wahrscheinlich zuerst verstehen, wann Sie sie im "richtigen" Fall oder bei Ihnen verwenden müssen werden sie in Zukunft aufgrund ihrer Wirkung anstelle ihrer Bedeutung benutzen.

In Ihrem Beispiel ... warum ist es suboptimal? Weil Sie versuchen, der Basisklasse einige Verhaltensweisen hinzuzufügen (verschieben, kollidieren usw.), wenn sie möglicherweise nicht angewendet werden und Sie keinen Code erneut verwenden. Sie erstellen eine Zusammenstellung von Verhaltensweisen, die implementiert werden können oder auch nicht (deshalb prüfen Sie vor dem Aufruf dieser Methoden auf null , BTW kann auf fieldName?.MethodName() vereinfacht werden). Sie lösen zur Laufzeit etwas auf, das zur Kompilierzeit perfekt bekannt ist, und Kompilierzeitprüfungen sind (fast) immer vorzuziehen.

Wenn jedes Objekt diese Logik implementieren muss (ich würde das lieber vermeiden), um den Aufrufpunkt zu vereinfachen, dann brauchen Sie nicht all diese Komplexität:

%Vor%

Im Allgemeinen schließen sich Komposition und Vererbung nicht aus und sie spielen am besten, wenn sie zusammen verwendet werden.

    
Adriano Repetti 21.06.2017 07:06
quelle

Tags und Links