Sollte ich es vermeiden, Objektvererbung zu verwenden, um ein Spiel zu entwickeln?

36

Ich bevorzuge OOP-Funktionen, wenn ich Spiele mit Unity entwickle. Normalerweise erstelle ich eine Basisklasse (meistens abstrahiert) und nutze die Objektvererbung, um die gleiche Funktionalität für die verschiedenen anderen Objekte zu nutzen.

Ich habe jedoch kürzlich von jemandem gehört, dass die Verwendung von Vererbung vermieden werden muss und wir stattdessen Schnittstellen verwenden sollten. Also fragte ich warum und er sagte, dass "Objektvererbung natürlich wichtig ist, aber wenn es viele erweiterte Objekte gibt, ist die Beziehung zwischen Klassen tief gekoppelt.

Ich verwende eine abstrakte Basisklasse, so etwas wie WeaponBaseund bestimmte Waffenklassen zu schaffen , wie Shotgun, AssaultRifle, Machinegunso ähnlich. Es gibt so viele Vorteile und ein Muster, das ich wirklich mag, ist Polymorphismus. Ich kann das von Unterklassen erstellte Objekt so behandeln, als ob es die Basisklasse wäre, damit die Logik drastisch reduziert und wiederverwendbarer wird. Wenn ich auf die Felder oder Methoden der Unterklasse zugreifen muss, kehre ich zur Unterklasse zurück.

Ich möchte nicht , gleiche Felder definieren, die üblicherweise in verschiedenen Klassen, wie AudioSource/ Rigidbody/ Animatorund viele Mitgliederfelder I definiert wie fireRate, damage, range, spreadund so weiter. In einigen Fällen können auch einige Methoden überschrieben werden. In diesem Fall verwende ich also virtuelle Methoden, sodass ich diese Methode im Grunde mit der Methode der übergeordneten Klasse aufrufen kann. Sollte sich die Logik bei untergeordneten Klassen jedoch unterscheiden, habe ich sie manuell überschrieben.

Sollten all diese Dinge als schlechte Praktiken aufgegeben werden? Soll ich stattdessen Schnittstellen verwenden?

modernator
quelle
30
"Es gibt so viele Vorteile, und ein Muster, das ich wirklich mag, ist Polymorphismus." Polymorphismus ist kein Muster. Es ist buchstäblich der ganze Sinn von OOP. Andere Dinge wie gemeinsame Felder usw. können leicht erreicht werden, ohne dass Code dupliziert oder vererbt werden muss.
Cubic
3
Hat Ihnen die Person, die Ihnen vorgeschlagen hat, dass Schnittstellen besser sind als Vererbung, irgendwelche Argumente geliefert? Ich bin neugierig.
Hawker65
1
@ Cubic Ich habe nie gesagt, dass Polymorphismus ein Muster ist. Ich sagte "... ein Muster, das ich wirklich genieße, ist der Polymorphismus". Es bedeutet, dass das Muster, das ich wirklich genieße, "Polymorphismus" verwendet, nicht, dass Polymorphismus ein Muster ist.
Modernator
4
Menschen werben mit Schnittstellen über Vererbung in irgendeiner Form der Entwicklung. Es erhöht jedoch tendenziell den Aufwand und die kognitive Dissonanz, führt zu einer Klassenexplosion, erhöht häufig den fragwürdigen Wert und führt in einem System normalerweise nicht zu einer guten Synergie, es sei denn, JEDER im Projekt folgt diesem Fokus auf Schnittstellen. Geben Sie also die Vererbung noch nicht auf. Spiele weisen jedoch einige einzigartige Merkmale auf, und von der Architektur von Unity wird die Verwendung eines CES-Designparadigmas erwartet. Aus diesem Grund sollten Sie Ihren Ansatz optimieren. Lesen Sie Eric Lipperts Wizards and Warriors-Reihe, in der Probleme mit dem Spieltyp beschrieben werden.
Dunk
2
Ich würde sagen, dass eine Schnittstelle ein besonderer Vererbungsfall ist, bei dem die Basisklasse keine Datenfelder enthält. Ich denke, der Vorschlag ist, nicht einen riesigen Baum zu bauen, in dem jedes Objekt klassifiziert werden muss, sondern viele kleine Bäume, die auf Schnittstellen basieren, in denen Sie die Mehrfachvererbung nutzen können. Polymorphismus geht bei Grenzflächen nicht verloren.
Emanuele Paolini

Antworten:

65

Bevorzugen Sie die Zusammensetzung gegenüber der Vererbung in Ihrem Entitäts- und Inventar- / Objektsystem. Dieser Rat gilt in der Regel für Spielelogikstrukturen, bei denen die Art und Weise, wie Sie komplexe Dinge (zur Laufzeit) zusammenstellen können, zu vielen verschiedenen Kombinationen führen kann. Dann bevorzugen wir die Komposition.

Bevorzugen Sie die Vererbung gegenüber der Komposition in Ihrer Logik auf Anwendungsebene, von UI-Konstrukten bis hin zu Diensten. Beispielsweise,

Widget->Button->SpinnerButton oder

ConnectionManager->TCPConnectionManagervs ->UDPConnectionManager.

... hier gibt es eine klar definierte Ableitungshierarchie und keine Vielzahl möglicher Ableitungen, sodass die Verwendung der Vererbung einfacher ist.

Fazit : Verwenden Sie Vererbung, wo Sie können, aber Komposition, wo Sie müssen. PS Der andere Grund, warum wir die Komposition in Entitätssystemen bevorzugen, ist, dass es normalerweise viele Entitäten gibt und die Vererbung für den Zugriff auf Mitglieder auf jedes Objekt einen Leistungsaufwand verursachen kann. siehe vtables .

Ingenieur
quelle
10
Vererbung bedeutet nicht, dass alle Methoden virtuell sind. Dies führt also nicht automatisch zu Leistungskosten. VTable spielt nur beim Verteilen der virtuellen Anrufe eine Rolle , nicht beim Zugreifen auf Datenmitglieder.
Ruslan
1
@ Ruslan Ich habe die Wörter 'may' und 'can' hinzugefügt, um Ihren Kommentar aufzunehmen. Ansonsten ging ich, um die Antwort kurz zu halten, nicht zu sehr ins Detail. Vielen Dank.
Ingenieur
7
P.S. The other reason we may favour composition in entity systems is ... performance: Ist das wirklich wahr? Wenn Sie sich die durch @Linaith verknüpfte Wikipedia-Seite ansehen, werden Sie feststellen, dass Sie Ihr Objekt aus Schnittstellen zusammenstellen müssen. Daher haben Sie (noch oder noch mehr) virtuelle Funktionsaufrufe und mehr Cache-Fehler, als Sie eine andere Indirektionsebene eingeführt haben.
Flamefire
1
@Flamefire Ja, es ist wahr; Es gibt Möglichkeiten, Ihre Daten so zu organisieren, dass Ihre Cache-Effizienz unabhängig von diesen zusätzlichen Indirektionen optimal und mit Sicherheit weitaus optimaler ist. Diese sind keine Vererbung und stellen eine Komposition dar, wenn auch eher im Sinne von Array-of-Struct / Struct-of-Arrays (verschachtelte / nicht verschachtelte Daten in cachelinearer Anordnung), die in separate Arrays aufgeteilt werden und manchmal Daten duplizieren, um Gruppen zuzuordnen von Operationen an verschiedenen Teilen Ihrer Pipeline. Aber hier noch einmal darauf einzugehen, wäre eine sehr große Ablenkung, die aus Gründen der Übersichtlichkeit am besten vermieden werden sollte.
Ingenieur
2
Bitte denken Sie daran, Kommentare zu zivilrechtlichen Aufforderungen zur Klärung oder Kritik aufzubewahren und den Inhalt der Ideen und nicht den Charakter oder die Erfahrung der Person , die sie vorgibt, zu diskutieren .
Josh
18

Sie haben bereits ein paar nette Antworten erhalten, aber der riesige Elefant im Raum in Ihrer Frage ist dieser:

von jemandem gehört, dass die Verwendung von Vererbung vermieden werden muss und wir stattdessen Schnittstellen verwenden sollten

Als Faustregel gilt: Wenn Ihnen jemand eine Faustregel gibt, ignorieren Sie diese. Dies gilt nicht nur für "jemand, der Ihnen etwas erzählt", sondern auch für das Lesen von Inhalten im Internet. Solcher Rat ist wertlos und oft sehr schädlich, es sei denn, Sie wissen warum (und können wirklich dahinter stehen).

Nach meiner Erfahrung sind die wichtigsten und hilfreichsten Konzepte in OOP "niedrige Kopplung" und "hohe Kohäsion" (Klassen / Objekte wissen so wenig wie möglich voneinander und jede Einheit ist für so wenig wie möglich verantwortlich).

Geringe Kopplung

Dies bedeutet, dass jedes "Bündel von Dingen" in Ihrem Code so wenig wie möglich von der Umgebung abhängen sollte. Dies gilt für Klassen (Klassenentwurf), aber auch für Objekte (tatsächliche Implementierung), "Dateien" im Allgemeinen (dh Anzahl #includes pro einzelne .cppDatei, Anzahl importpro einzelne .javaDatei usw.).

Ein Zeichen dafür, dass zwei Entitäten gekoppelt sind, ist, dass eine von ihnen unterbrochen wird (oder geändert werden muss), wenn die andere in irgendeiner Weise geändert wird.

Vererbung erhöht offensichtlich die Kopplung; Durch Ändern der Basisklasse werden alle Unterklassen geändert.

Schnittstellen reduzieren die Kopplung: Indem Sie einen klaren, methodenbasierten Vertrag definieren, können Sie alles an beiden Seiten der Schnittstelle frei ändern, solange Sie den Vertrag nicht ändern. (Beachten Sie, dass "interface" ein allgemeines Konzept ist. Die interfaceabstrakten Java- oder C ++ - Klassen sind lediglich Implementierungsdetails.)

Hoher Zusammenhalt

Dies bedeutet, dass jede Klasse, jedes Objekt, jede Datei usw. so wenig wie möglich betroffen oder dafür verantwortlich ist. Das heißt, vermeiden Sie große Klassen, die viel zu tun haben. Wenn Ihre Waffen in Ihrem Beispiel völlig unterschiedliche Aspekte haben (Munition, Schussverhalten, grafische Darstellung, Inventardarstellung usw.), können Sie verschiedene Klassen haben, die genau eines dieser Dinge darstellen. Die Hauptwaffenklasse verwandelt sich dann in einen "Inhaber" dieser Details; Ein Waffenobjekt ist dann kaum mehr als ein paar Zeiger auf diese Details.

In diesem Beispiel würden Sie sicherstellen, dass Ihre Klasse, die das "Feuerverhalten" darstellt, so wenig wie möglich über die Hauptwaffenklasse weiß. Optimalerweise gar nichts. Dies würde zum Beispiel bedeuten, dass Sie jedem Objekt in Ihrer Welt (Türmen, Vulkanen, NSCs ...) mit einem Fingerschnipp "Feuerverhalten" verleihen könnten . Wenn Sie irgendwann ändern möchten, wie Waffen im Inventar dargestellt werden, können Sie dies einfach tun - nur Ihre Inventarklasse weiß darüber Bescheid.

Ein Zeichen dafür, dass eine Entität nicht zusammenhängend ist, ist, dass sie immer größer wird und sich gleichzeitig in mehrere Richtungen verzweigt.

Erbschaft, wie Sie sie beschreiben, verringert den Zusammenhalt - Ihre Waffenklassen sind letztendlich große Brocken, die alle möglichen unterschiedlichen, nicht verwandten Aspekte Ihrer Waffen behandeln.

Schnittstellen erhöhen indirekt den Zusammenhalt, indem sie Verantwortlichkeiten zwischen den beiden Seiten der Schnittstelle klar aufteilen.

Was nun

Es gibt immer noch keine festen Regeln, all dies sind nur Richtlinien. Im Allgemeinen wird, wie der Benutzer TKK in seiner Antwort erwähnte, die Vererbung viel in der Schule und in Büchern unterrichtet. es ist das schicke Zeug über OOP. Das Lehren von Interfaces ist wahrscheinlich langweiliger und erschließt (wenn Sie an trivialen Beispielen vorbeigehen) das Feld der Abhängigkeitsinjektion, das nicht so eindeutig ist wie die Vererbung.

Letztendlich ist Ihr vererbungsbasiertes Schema immer noch besser, als überhaupt kein klares OOP-Design zu haben. Fühlen Sie sich also frei, dabei zu bleiben. Wenn Sie möchten, können Sie ein wenig über niedrige Kopplung, hohe Kohäsion nachdenken und nachsehen, ob Sie Ihrem Arsenal diese Art des Denkens hinzufügen möchten. Sie können jederzeit nacharbeiten, um dies zu einem späteren Zeitpunkt auszuprobieren. Oder probieren Sie schnittstellenbasierte Ansätze in Ihrem nächsten größeren neuen Codemodul aus.

AnoE
quelle
Vielen Dank für die ausführliche Erklärung. Es ist sehr hilfreich zu verstehen, was ich vermisse.
Modernator
13

Die Idee, dass Vererbung vermieden werden muss, ist einfach falsch.

Es gibt ein Kodierungsprinzip namens Komposition über Vererbung . Darin heißt es, dass Sie mit Komposition das Gleiche erreichen können, und dies ist vorzuziehen, da Sie einen Teil des Codes wiederverwenden können. Siehe Warum sollte ich die Komposition der Vererbung vorziehen?

Ich muss sagen, ich mag deine Waffenklassen und würde es genauso machen. Aber ich habe noch kein Spiel gemacht ...

Wie von James Trotter hervorgehoben, könnte die Komposition einige Vorteile haben, insbesondere in Bezug auf die Flexibilität zur Laufzeit, um die Funktionsweise der Waffe zu ändern. Dies wäre mit Vererbung möglich, aber es ist schwieriger.

Linaith
quelle
14
Anstatt Klassen für die Waffen zu haben, könnten Sie eine einzige "Waffen" -Klasse haben und Komponenten für die Handhabung dessen, was das Schießen tut, welche Anhänge es hat und was sie tun, welchen "Munitionsspeicher" es hat, Sie könnten viele Konfigurationen aufbauen Dies kann zum Teil eine "Schrotflinte" oder ein "Sturmgewehr" sein, kann aber auch zur Laufzeit geändert werden, um beispielsweise Aufsätze auszutauschen und die Magazinkapazitäten zu ändern nicht einmal ursprünglich gedacht. Ja, mit Vererbung kann man etwas Ähnliches erreichen, aber nicht so einfach.
Trotzki94
3
@ JamesTrotter An diesem Punkt denke ich an Borderlands und seine Kanonen und frage mich, was für eine Monstrosität dies mit nur Erbe sein würde ...
Baldrickk
1
@ JamesTrotter Ich wünschte, das wäre eine Antwort und kein Kommentar.
Pharap
6

Das Problem ist, dass Vererbung zu Kopplung führt - Ihre Objekte müssen mehr voneinander wissen. Deshalb lautet die Regel "Komposition immer vor Vererbung bevorzugen". Dies bedeutet nicht, NIEMALS Vererbung zu verwenden, sondern nur, wo es völlig angemessen ist. Wenn Sie jedoch jemals dagesitzen und denken, "Ich könnte dies auf beide Arten tun, und beide ergeben einen Sinn", gehen Sie direkt zur Komposition über.

Vererbung kann auch einschränkend sein. Sie haben eine "WeaponBase", die ein AssultRifle sein kann - fantastisch. Was passiert, wenn Sie eine Doppellauf-Schrotflinte haben und die Läufe unabhängig schießen lassen möchten? Etwas härter, aber machbar, Sie haben nur eine Klasse mit einem Lauf und zwei Läufen, aber Sie können nicht einfach zwei Läufe montieren auf der gleichen Waffe, können Sie? Könntest du einen so umbauen, dass er 3 Fässer hat oder brauchst du dafür eine neue Klasse?

Was ist mit einem AssultRifle mit einem GrenadeLauncher darunter - hmm, etwas härter. Können Sie den GrenadeLauncher durch eine Taschenlampe für die Nachtjagd ersetzen?

Wie können Sie Ihrem Benutzer schließlich erlauben, die vorhergehenden Waffen zu erstellen, indem Sie eine Konfigurationsdatei bearbeiten? Dies ist schwierig, da Sie Beziehungen fest programmiert haben, die möglicherweise besser mit Daten erstellt und konfiguriert sind.

Die mehrfache Vererbung kann einige dieser trivialen Beispiele in gewisser Weise beheben, fügt jedoch eine Reihe eigener Probleme hinzu.

Bevor es übliche Redewendungen wie "Komposition vor Vererbung" gab, habe ich dies herausgefunden, indem ich einen übermäßig komplexen Vererbungsbaum geschrieben habe (was super Spaß machte und perfekt funktionierte) und dann herausgefunden habe, dass es wirklich schwierig war, ihn später zu ändern und zu pflegen. Das Sprichwort ist nur so, dass Sie eine alternative und kompakte Möglichkeit haben, ein solches Konzept zu lernen und sich daran zu erinnern, ohne die gesamte Erfahrung durchlaufen zu müssen. Wenn Sie jedoch stark auf Vererbung setzen möchten, empfehle ich, nur offen zu bleiben und zu bewerten, wie gut es funktioniert für Sie - nichts Schlimmes wird passieren, wenn Sie stark vererbt werden und es im schlimmsten Fall eine großartige Lernerfahrung sein könnte (im besten Fall funktioniert es gut für Sie)

Bill K
quelle
2
"Wie können Sie Ihrem Benutzer erlauben, die vorhergehenden Waffen zu erstellen, indem Sie eine Konfigurationsdatei bearbeiten?" -> Das Muster, das ich im Allgemeinen mache, ist, eine letzte Klasse für jede Waffe zu erstellen. Zum Beispiel wie Glock17. Erstellen Sie zuerst die WeaponBase-Klasse. Diese Klasse ist abstrahiert und definiert allgemeine Werte wie Feuermodus, Feuerrate, Magazingröße, maximale Munition, Pellets. Es behandelt auch Benutzereingaben wie mouse1 / mouse2 / reload und ruft die entsprechende Methode auf. Sie sind fast virtuell oder auf mehrere Funktionen aufgeteilt, sodass Entwickler die Basisfunktionalität bei Bedarf überschreiben können. Und dann erstellen Sie eine weitere Klasse, die WeaponBase,
Modernator
2
so etwas wie SlideStoppableWeapon. Die meisten Pistolen haben dies, und dies behandelt zusätzliche Verhaltensweisen wie Rutschen gestoppt. Erstellen Sie schließlich die Glock17-Klasse, die sich über SlideStoppableWeapon erstreckt. Glock17 ist die letzte Klasse, wenn die Waffe eine einzigartige Fähigkeit hat, die nur hier endgültig eingeschrieben ist. Normalerweise benutze ich dieses Muster, wenn die Waffe einen speziellen Feuermechanismus oder einen Nachlademechanismus hat. Implementieren Sie dieses einzigartige Verhalten, um die Klassenhierarchie zu beenden, und kombinieren Sie so viele gemeinsame Logiken wie möglich von den Eltern.
Modernator
2
@modernator Wie gesagt, es ist nur ein Leitfaden - wenn Sie ihm nicht vertrauen, können Sie ihn einfach ignorieren, die Ergebnisse im Laufe der Zeit nicht aus den Augen verlieren und von Zeit zu Zeit den Nutzen gegen die Schmerzen abwägen. Ich versuche, mich an Orte zu erinnern, an denen ich mir die Zehen gestoßen habe, und helfe anderen, dasselbe zu vermeiden, aber was für mich funktioniert, funktioniert möglicherweise nicht für Sie.
Bill K
7
@modernator In Minecraft haben sie Monstereine Unterklasse von gemacht WalkingEntity. Dann fügten sie Schleime hinzu. Wie gut hat das wohl funktioniert?
user253751
1
@immibis Haben Sie eine Referenz oder einen anekdotischen Link zu diesem Problem? Googeln minecraft Schlüsselwörter ertrinkt mich in Video - Links ;-)
Peter - wieder einzusetzen Monica
3

Im Gegensatz zu den anderen Antworten hat dies nichts mit Vererbung oder Komposition zu tun. Vererbung vs. Komposition ist eine Entscheidung, die Sie hinsichtlich der Implementierung einer Klasse treffen. Schnittstellen vs. Klassen ist eine Entscheidung, die davor liegt.

Interfaces sind die erstklassigen Bürger jeder OOP-Sprache. Klassen sind sekundäre Implementierungsdetails. Neue Programmierer werden von Lehrern und Büchern, die Klassen und Vererbung vor Schnittstellen einführen, in den Bann gezogen.

Das Schlüsselprinzip ist, dass die Typen aller Methodenparameter und Rückgabewerte nach Möglichkeit Schnittstellen und keine Klassen sein sollten. Dies macht Ihre APIs viel flexibler und leistungsfähiger. In den allermeisten Fällen sollten Ihre Methoden und ihre Aufrufer keinen Grund haben, die tatsächlichen Klassen der Objekte, mit denen sie sich befassen, zu kennen oder sich darum zu kümmern. Wenn Ihre API nicht mit Implementierungsdetails vertraut ist, können Sie frei zwischen Komposition und Vererbung wechseln, ohne etwas zu beschädigen.

Nein U
quelle
Wenn Benutzeroberflächen die erstklassigen Bürger einer OOP-Sprache sind, frage ich mich, ob Python, Ruby, ... keine OOP-Sprachen sind.
Blackjack
@BlackJack: In Ruby, die Schnittstelle ist implizit (Ente eingeben , wenn man so will), und ausgedrückt durch die Zusammensetzung anstelle von Vererbung (auch, es hat Mixins , die zwischen irgendwo sind, soweit ich betroffen bin) ...
AnoE
@BlackJack "Interface" (das Konzept) erfordert nicht interface(das Sprachschlüsselwort).
Caleth
2
@Caleth Aber sie als erstklassige Bürger zu bezeichnen, ist meiner Meinung nach richtig. Wenn eine Sprachbeschreibung behauptet, dass etwas ein erstklassiger Bürger ist , bedeutet dies normalerweise, dass es eine echte Sache in dieser Sprache ist, die einen Typ und Wert hat und weitergegeben werden kann. So wie Klassen in Python erstklassige Bürger sind, sind auch Funktionen, Methoden und Module. Wenn wir über das Konzept sprechen, dann sind Schnittstellen auch Teil aller Nicht-OOP-Sprachen.
Blackjack
2

Wann immer Ihnen jemand sagt, dass ein spezifischer Ansatz für alle Fälle der beste ist, ist es das Gleiche, als würde er Ihnen sagen, dass ein und dasselbe Arzneimittel alle Krankheiten heilt.

Vererbung vs Komposition ist eine Is-A-vs-Has-A-Frage. Schnittstellen sind ein weiterer (dritter) Weg, der für einige Situationen geeignet ist.

Vererbung oder die is-a-Logik: Sie verwenden sie, wenn sich die neue Klasse verhält und vollständig (äußerlich) wie die alte Klasse verwendet wird, von der Sie sie ableiten, wenn die neue Klasse der Öffentlichkeit zugänglich gemacht wird all die Funktionalität, die die alte Klasse hatte ... dann erben Sie.

Komposition oder die Has-A-Logik: Wenn die neue Klasse die alte Klasse nur intern verwenden muss, ohne die Features der alten Klasse der Öffentlichkeit zugänglich zu machen, verwenden Sie Komposition, dh, Sie haben eine Instanz der alten Klasse als Mitgliedseigenschaft oder eine Variable der neuen. (Dies kann je nach Anwendungsfall ein privates oder geschütztes Eigentum sein oder was auch immer). Der entscheidende Punkt hierbei ist, dass dies der Anwendungsfall ist, in dem Sie die ursprünglichen Klassenfeatures nicht der Öffentlichkeit zugänglich machen und verwenden möchten, sondern sie nur intern verwenden möchten, während Sie sie im Fall der Vererbung der Öffentlichkeit über die neue Klasse. Manchmal braucht man einen Ansatz und manchmal den anderen.

Schnittstellen: Schnittstellen sind ein weiterer Anwendungsfall - wenn die neue Klasse die Funktionalität der alten Klasse teilweise und nicht vollständig implementieren und der Öffentlichkeit zugänglich machen soll. Auf diese Weise können Sie eine neue Klasse erstellen, eine Klasse mit einer völlig anderen Hierarchie als die alte, die sich nur in einigen Punkten wie die alte verhält.

Angenommen, Sie haben verschiedene Kreaturen, die durch Klassen dargestellt werden, und Funktionen, die durch Funktionen dargestellt werden. Zum Beispiel würde ein Vogel Talk (), Fly () und Poop () haben. Class Duck würde von Class Bird erben und alle Funktionen implementieren.

Class Fox kann offensichtlich nicht fliegen. Wenn Sie also definieren, dass der Vorfahr alle Features hat, können Sie den Nachfahren nicht richtig ableiten.

Wenn Sie jedoch die Features in Gruppen aufteilen, die jede Gruppe von Aufrufen mit einer Schnittstelle darstellen, z. B. IFly, die Takeoff (), FlapWings () und Land () enthält, können Sie für die Klasse Fox Funktionen von ITalk und IPoop implementieren aber nicht IFly. Sie würden dann Variablen und Parameter definieren, um Objekte zu akzeptieren, die eine bestimmte Schnittstelle implementieren, und dann würde der Code, der mit ihnen arbeitet, wissen, wie er aufgerufen werden kann ... und immer nach anderen Schnittstellen fragen können, wenn geprüft werden muss, ob andere Funktionen ebenfalls vorhanden sind für das aktuelle Objekt implementiert.

Jeder dieser Ansätze hat Anwendungsfälle, bei denen es sich um den besten handelt. Kein Ansatz ist eine absolut beste Lösung für alle Fälle.

Dragan Juric
quelle
1

Für ein Spiel, insbesondere mit Unity, das mit einer Entity-Komponenten-Architektur arbeitet, sollten Sie für Ihre Spielkomponenten die Komposition der Vererbung vorziehen. Es ist viel flexibler und verhindert, dass Sie in eine Situation geraten, in der eine Spieleinheit zwei Dinge sein soll, die sich auf verschiedenen Blättern eines Vererbungsbaums befinden.

Angenommen, Sie haben eine TalkingAI in einem Zweig eines Vererbungsbaums und VolumetricCloud in einem anderen Zweig und möchten eine sprechende Cloud. Dies ist bei einem tiefen Vererbungsbaum schwierig. Mit der Entitätskomponente erstellen Sie einfach eine Entität mit TalkingAI- und Cloud-Komponenten, und schon kann es losgehen.

Dies bedeutet jedoch nicht, dass Sie in Ihrer volumetrischen Cloud-Implementierung beispielsweise keine Vererbung verwenden sollten. Der Code dafür ist möglicherweise umfangreich und besteht aus mehreren Klassen. Sie können OOP nach Bedarf verwenden. Aber es wird alles eine einzelne Spielkomponente sein.

Als Randnotiz nehme ich ein Problem damit:

Normalerweise erstelle ich eine Basisklasse (meistens abstrahiert) und nutze die Objektvererbung, um die gleiche Funktionalität für die verschiedenen anderen Objekte zu nutzen.

Die Vererbung ist nicht für die Wiederverwendung von Code vorgesehen. Es ist für die Einrichtung eines ist-eine Beziehung. Diese gehen oft Hand in Hand, aber Sie müssen vorsichtig sein. Nur weil zwei Module möglicherweise denselben Code verwenden müssen, bedeutet dies nicht, dass eines vom selben Typ ist wie das andere.

Sie können Schnittstellen verwenden, wenn Sie beispielsweise eine List<IWeapon>mit verschiedenen Waffentypen verwenden möchten . Auf diese Weise können Sie sowohl von der IWeapon-Oberfläche als auch von der MonoBehaviour-Unterklasse für alle Waffen erben und Probleme vermeiden, wenn keine Mehrfachvererbung vorliegt.

Sava B.
quelle
-3

Sie scheinen nicht zu verstehen, was eine Schnittstelle ist oder tut. Es dauerte mehr als 10 Jahre, bis ich über OO nachgedacht und einige wirklich kluge Leute gefragt hatte, ob ich in der Lage war, einen ah-ha-Moment mit Schnittstellen zu verbringen. Hoffe das hilft.

Das Beste ist, eine Kombination von ihnen zu verwenden. In meinem OO Mind Modeling lassen sich die Welt und die Menschen sagen, ein tiefes Beispiel. Die Seele ist durch den Geist mit dem Körper verbunden. Der Geist IST die Schnittstelle zwischen der Seele (manche betrachten den Intellekt) und den Befehlen, die sie an den Körper ausgibt. Die Schnittstelle des Geistes zum Körper ist funktionell für alle Menschen gleich.

Dieselbe Analogie kann zwischen der Person (Körper und Seele) verwendet werden, die mit der Welt in Kontakt steht. Der Körper ist die Schnittstelle zwischen Geist und Welt. Jeder Mensch verbindet sich auf die gleiche Weise mit der Welt. Die einzige Einzigartigkeit ist, wie wir mit der Welt interagieren. Auf diese Weise entscheiden wir mit unserem VERSTÄNDNIS, wie wir mit der Welt interagieren.

Eine Schnittstelle ist dann einfach ein Mechanismus, um Ihre OO-Struktur mit einer anderen unterschiedlichen OO-Struktur in Beziehung zu setzen, wobei jede Seite unterschiedliche Attribute aufweist, die miteinander verhandelt / abgebildet werden müssen, um eine funktionale Ausgabe in einem anderen Medium / einer anderen Umgebung zu erzeugen.

Damit der Verstand die Open-Hand-Funktion aufrufen kann, muss er die Schnittstelle nervous_command_system implementieren, die über eine eigene API verfügt, die Anweisungen zum Implementieren aller erforderlichen Anforderungen vor dem Aufruf von Open-Hand enthält.

Christopher Hoffman
quelle