Können Sie "leere" Abstracts / Klassen haben?

10

Natürlich können Sie das. Ich frage mich nur, ob es rational ist, so zu gestalten.

Ich mache einen Breakout-Klon und mache ein Klassendesign. Ich wollte die Vererbung verwenden, obwohl ich das nicht muss, um das anzuwenden, was ich in C ++ gelernt habe. Ich habe über Klassendesign nachgedacht und mir so etwas ausgedacht:

GameObject -> Basisklasse (besteht aus Datenelementen wie x- und y-Offsets und einem Vektor von SDL_Surface *

MovableObject: GameObject -> abstrakte Klasse + abgeleitete Klasse von GameObject (eine Methode void move () = 0;)

NonMovableObject: GameObject -> leere Klasse ... keine anderen Methoden oder Datenelemente als Konstruktor und Destruktor (zumindest für den Moment?).

Später plante ich, eine Klasse von NonMovableObject abzuleiten, wie Tileset: NonMovableObject. Ich habe mich nur gefragt, ob häufig "leere" abstrakte Klassen oder nur leere Klassen verwendet werden ... Ich stelle fest, dass ich auf diese Weise nur die Klasse NonMovableObject nur zur Kategorisierung erstelle.

Ich weiß, dass ich Dinge überdenke, nur um einen Breakout-Klon zu erstellen, aber mein Fokus liegt weniger auf dem Spiel als vielmehr auf der Verwendung von Vererbung und dem Entwerfen einer Art Spiel-Framework.

ShrimpCrackers
quelle

Antworten:

2

In C ++ haben Sie Mehrfachvererbung, so dass es buchstäblich keinen Vorteil hat, diese leeren Klassen zu haben. Wenn Sie möchten, dass Ball später von GameObject und MovableObject erbt (z. B. möchten Sie beispielsweise eine Reihe von GameObjects halten und alle n-Sekunden eine Tick-Methode aufrufen, damit sich alle bewegen), ist dies einfach genug.

Aber in dieser Situation würde ich persönlich vorschlagen, dass Sie sich stattdessen an das Axiom " Komposition (Kapselung) gegenüber Vererbung bevorzugen " erinnern und sich das Zustandsmuster ansehen . Wenn Sie möchten, dass jedes Objekt später seinen Bewegungsstatus hat, übergeben Sie es an das GameObject. Zum Beispiel: DiagonalBouncingMovementState für den Ball, HoriazontalControlledMovementState für das Paddel, NoMovementState für einen Stein.

1) Das macht es einfacher für Sie zu schreiben Unit - Tests , wenn Sie (die wählen, wieder, ich würde empfehlen) - weil Sie Gameobject und jede Ihrer Staaten unabhängig testen.

2) Angenommen, Sie haben Token, die von den Steinen fallen, aber dann möchten Sie einen davon so ändern , dass sie sich diagonal bewegen. Anstatt ihn um eine komplexe Hierarchie der Klassenvererbung zu verschieben, können Sie einfach den fest codierten Bewegungsstatus ändern, der an ihn übergeben wird .

3) Am wichtigsten: Wenn Sie im Spiel ändern möchten, wie sich der Ball und / oder das Paddel basierend auf diesen Token bewegen, ändern Sie einfach seinen Bewegungsstatus (DiagonalMovementState wird zu DiagonalWithGravityMovementState).

Nichts davon deutete darauf hin, dass die Vererbung immer schlecht ist, nur um zu demonstrieren, warum wir die Kapselung der Vererbung vorziehen. Möglicherweise möchten Sie dennoch jeden Objekttyp von GameObject ableiten und diese Klassen ihren anfänglichen Bewegungsstatus verstehen lassen. Sie werden mit ziemlicher Sicherheit jeden Ihrer Bewegungszustände aus einer abstrakten MovementState-Klasse ableiten wollen, da C ++ keine Schnittstellen hat.

$ 0,02

pdr
quelle
8

In Java werden "leere" Schnittstellen als Markierungen verwendet (z. B. serialisierbar), da Objekte zur Laufzeit überprüft werden können, ob sie diese Schnittstelle "implementieren" oder nicht. aber in C ++ scheint es mir ziemlich sinnlos.

IMO, Sprachfunktionen sollten als Werkzeuge betrachtet werden, die Ihnen helfen, bestimmte Ziele zu erreichen, und nicht als Verpflichtungen. Gerade weil Sie können eine Funktion verwenden, bedeutet nicht , Sie müssen . Sinnlose Vererbung macht ein Programm nicht besser.

user281377
quelle
3

Du bist nicht überlegt darüber nachzudenken; du denkst und das ist gut.

Verwenden Sie, wie bereits erwähnt, keine Sprachfunktion, nur weil sie vorhanden ist. Kompromisse beim Erlernen von Sprachfunktionen sind der Schlüssel zu gutem Design.

Basisklassen sollten nicht zur Kategorisierung verwendet werden. Dies könnte bedeuten, dass der Typ überprüft wird. Das ist eine schlechte Idee.

Schreiben Sie reine Abstraktionen, die den Vertrag Ihrer Objekte definieren. Der Vertrag Ihrer Objekte ist die nach außen gerichtete Schnittstelle. Seine öffentliche Schnittstelle. Was es macht.

Hinweis: Es ist wichtig zu verstehen, dass dies nicht die Daten sind x, ysondern die Funktionen move().

In Ihrem Design haben Sie eine Klasse GameObject. Sie verlassen sich auf seine Daten und nicht auf seine Funktionalität. Es fühlt sich effizient und korrekt an, die Vererbung zum Teilen von scheinbar gemeinsamen Daten zu verwenden, in Ihrem Fall xund y.

Dies ist nicht die richtige Verwendung der Vererbung. Denken Sie immer daran, dass Vererbung die engste Form der Kopplung ist, die Sie möglicherweise haben können, und Sie sollten jederzeit nach einer losen Kopplung streben.

Es NonMovableObjectbewegt sich von Natur aus nicht. Sein xund ysollte mit Sicherheit erklärt werden const. Es muss sich von Natur aus MoveableObjectbewegen. Es kann seine xund yals nicht deklarieren const. Dies sind also nicht dieselben Daten und sie können nicht gemeinsam genutzt werden.

Berücksichtigen Sie die Funktionalität oder den Vertrag Ihrer Objekte. Was sollen sie tun ? Wenn Sie das richtig machen, werden die Daten kommen. Arbeiten Sie jeweils an einem Objekt. Sorgen Sie sich nacheinander um jeden. Sie werden feststellen, dass Ihre guten Designs wiederverwendbar sind.

Vielleicht gibt es überhaupt keine NonMovableObject. Was ist mit einer reinen Abstraktion GameObjectBase, die eine move()Methode definiert ? Ihr System instanziiert GameObjects, die implementiert werden, move()und das System verschiebt nur diejenigen, die verschoben werden müssen.

Dies ist nur der Anfang; ein Geschmack. Das Kaninchenloch geht viel tiefer. Da ist kein Löffel.

Hiwaylon
quelle
Zwar können weder voneinander MovableObjectnoch NonMovableObjectvoneinander erben, doch haben beide einen Standort. Als solche sollten beide durch Code konsumiert werden können, der z. B. das Ziel eines Monsters auf ein Objekt richten möchte, ohne Rücksicht darauf, ob sich dieses Objekt bewegen könnte oder nicht.
Supercat
2

Es hat Anwendungen in C # wie ammoQ erwähnt, aber in C ++ wäre die einzige Anwendung, wenn Sie eine Liste von NonMovableObject-Zeigern haben möchten.

Aber dann würde ich mir vorstellen, dass Sie die Objekte über den NonMoveableObject-Zeiger zerstören würden. In diesem Fall würden Sie virtuelle Destruktoren benötigen und es wäre keine leere Klasse mehr. :) :)

Tenpn
quelle
Ich habe auch über diesen Fall nachgedacht, aber was können Sie mit einer solchen Liste von Objekten tun, die kein Verhalten aufweisen? Höchstwahrscheinlich würden Sie irgendwie den tatsächlichen Typ herausfinden und eine Besetzung verwenden, um auf die verfügbaren Methoden und Felder zuzugreifen - definitiv ein Codegeruch.
user281377
Ja, das ist ein guter Punkt.
Tenpn
1

Eine Situation, in der Sie dies möglicherweise sehen, besteht darin, dass eine stark tyupierte Sammlung ansonsten heterogene Typen enthalten soll. Ich sage nicht, dass dies eine großartige Idee ist, sie kann auf eine schwache Abstraktion oder ein schlechtes Design hinweisen, aber es passiert. Wie Sie bereits erwähnt haben, ist dies eine Möglichkeit, Dinge zu kategorisieren, und es kann nützlich und vollkommen gültig sein.

ZB Sie möchten eine Sammlung für Katzen und Hunde. In Ihrem Design entscheiden Sie, dass sie viel gemeinsam haben. Haben Sie also eine Basisklasse von Mamal und verwenden Sie diesen Typ in Ihrer Sammlung (z. B. Liste). Alles gut. Dann entscheiden Sie, dass Sie Ihrer Sammlung einige Frösche hinzufügen möchten, die jedoch keine Säugetiere sind. Eine Möglichkeit, dies zu tun, besteht darin, die Unterklasse der Frösche und Säugetiere zu Tier zu machen, aber vielleicht können Sie nicht an viel denken, was die Frösche und Säugetiere enthalten häufig, damit das Tier leer bleibt. Sie tippen dann Ihre Sammlung mit Animal anstelle von Mamal und alles ist gut.

Im wirklichen Leben stelle ich fest, dass selbst wenn ich früher oder später eine leere Basisklasse erstelle, einige Funktionen dort landen, aber es gibt sicherlich Zeiten, in denen sie existieren.

Steve
quelle
Eine Frage ist jedoch: Wenn die Typen nichts gemeinsam haben, warum werden sie dann in derselben Sammlung aufbewahrt? Es gibt keine Operation, die Sie für alle Elemente in der Sammlung ausführen können, was den Zweck einer solchen Sammlung irgendwie zunichte macht. Jetzt gibt es möglicherweise einige künstliche Operationen, die Sie zu beiden hinzufügen möchten, um die Logik zu vereinfachen - z. B. das Implementieren des Besucherentwurfsmusters - und an diesem Punkt könnte es wieder nützlich werden, aber jetzt haben sie etwas gemeinsam ...
Jules