Ich habe mehrere Threads über dynamische Umwandlungen in C ++ gelesen, die alle voller Leute sind, die behaupten, dass sie schlechtes Design anzeigen. In anderen Sprachen habe ich nie viel darüber nachgedacht, den Typ eines Objekts zu überprüfen. Ich benutze es nie als Alternative zum Polymorphismus und nur dann, wenn eine starke Kopplung vollkommen akzeptabel scheint. Eine dieser Situationen treffe ich ziemlich oft: Ich habe eine Liste (ich benutze std :: vector in C ++) von Objekten, die alle von einer gemeinsamen Basisklasse abgeleitet sind. Die Liste wird von einem Objekt verwaltet, das die verschiedenen Unterklassen kennen darf (oft handelt es sich um eine kleine Hierarchie privater Klassen innerhalb der Klasse der verwaltenden Objekte). Indem ich sie in einer einzigen Liste (Array, Vektor, ..) belasse, kann ich immer noch von Polymorphie profitieren, aber wenn eine Operation auf Objekte einer bestimmten Unterklasse einwirken soll, verwende ich eine dynamische Darstellung oder etwas Ähnliches.
Gibt es einen anderen Ansatz für diese Art von Problem ohne dynamische Umwandlungen oder Typprüfungen, die ich vermisse? Ich bin wirklich neugierig, wie Programmierer, die diese um jeden Preis vermeiden, damit umgehen würden.
Wenn meine Beschreibung zu abstrakt ist, könnte ich ein einfaches Beispiel in C ++ schreiben (Bearbeiten: siehe unten).
%Vor%Es ist eine vereinfachte Version einer Klasse, die ich für die Kollisionserkennung in einem Spiel verwende, das box2d-Physik verwendet. Ich hoffe, dass die Box2D-Details nicht zu sehr von dem ablenken, was ich zu zeigen versuche. Ich habe eine sehr ähnliche Klasse 'Event', die verschiedene Arten von Event-Handlern erzeugt, die auf die gleiche Weise strukturiert sind (mit Unterklassen einer Basisklasse EventHandler anstelle von EntityContact).
Zumindest aus meiner Sicht existiert dynamic_cast
aus einem bestimmten Grund, und es gibt Zeiten, in denen es sinnvoll ist, sie zu benutzen. Dies könnte einer dieser Zeiten sein.
In Anbetracht der beschriebenen Situation kann eine mögliche Alternative sein, mehr Operationen in der Basisklasse zu definieren, aber sie als (möglicherweise stillschweigend) fehlgeschlagen zu definieren wenn Sie sie für die Basisklasse oder andere Klassen aufrufen, die diese Operationen nicht unterstützen.
Die wirkliche Frage ist, ob es sinnvoll ist, Ihre Operationen auf diese Weise zu definieren. Zurück zur typischen Tierhierarchie, wenn Sie mit Birds arbeiten, ist es oft sinnvoll für die Bird-Klasse, ein fly
-Mitglied zu definieren, und für die wenigen Vögel, die nicht fliegen können, muss es einfach scheitern (theoretisch sollte so etwas wie attempt_to_fly
umbenannt werden, was aber selten viel bewirkt).
Wenn Sie eine Menge davon sehen, deutet dies auf einen Mangel an Abstraktion in Ihren Klassen hin - zum Beispiel möchten Sie anstelle von fly
oder attempt_to_fly
wirklich ein Mitglied travel
, und es liegt beim einzelnen Tier zu entscheiden, ob es das durch Schwimmen, Krabbeln, Laufen, Fliegen usw. tun soll.
Dies
aber wenn eine Operation auf Objekte eines bestimmten Objekts wirken soll Unterklasse verwende ich eine dynamische Besetzung oder etwas ähnliches
klingt wie die Objektmodellierung ist nicht korrekt. Sie haben eine Liste, die Unterklassen-Instanzen enthält, aber sie sind nicht wirklich Unterklassen, da Sie nicht alle auf dieselbe Art und Weise (Liskov usw.) handeln können.
Eine mögliche Lösung besteht darin, Ihre Basisklasse so zu erweitern, dass Sie über eine Reihe von Nicht-Op-Methoden verfügen, die bestimmte Unterklassen außer Kraft setzen können. Aber das hört sich immer noch nicht richtig an.
Die Frage ist ziemlich weit gefasst, also einige allgemeine Punkte zu beachten:
dynamic_cast
ergibt bestimmte Kosten. Wenn es sich um die Laufzeitleistung handelt, überprüfen Sie die Kosten der Alternativen. Normalerweise würden Sie in solchen Situationen keine Liste abgeleiteter Objekte verwalten, sondern eine Liste von Interface-Zeigern (oder Basis-Zeigern), die sich auf die eigentlichen abgeleiteten Objekte beziehen. Wenn Sie einen Vorgang für ein bestimmtes Objekt ausführen möchten, greifen Sie über seine Schnittstelle / Basisklasse darauf zu, die alle erforderlichen Funktionen bereitstellen sollte. Die abgeleiteten Klassen sollten die Exposed Base-Funktionalität mit einer bestimmten Implementierung überschreiben.
Die Verwendung von dynamic_cast ist das Symptom, ein Entwurfsmuster wie Visitor oder Command implementieren zu wollen. Daher würde ich Refactoring empfehlen, um dies sichtbar zu machen.
Wie bei jedem Ansatz, der als schlechte Programmierung angesehen wird, gibt es immer einige Ausnahmen. Wenn ein Programmierer sagt: "Das und das ist böse, und du solltest es nie tun", dann meinen sie wirklich "es gibt fast nie einen Grund, so oder so etwas zu verwenden, und du solltest besser in der Lage sein, dich selbst zu erklären." Ich selbst bin nie auf eine Situation gestoßen, in der ein dynamic_cast
absolut notwendig ist und nicht leicht umstrukturiert werden kann. Aber wenn es den Job erledigt, dann sei es.
Da wir Ihr spezifisches Problem nicht kennen, kann ich Ihnen nur Rat geben, was Sie uns gesagt haben. Sie sagen, Sie haben einen Behälter mit polymorphen Basistypen. Zum Beispiel:
%Vor% Wenn Sie eines der Objekte, die Sie in diesem Container ableiten, auf bestimmte Weise verwenden, ist es nicht wirklich ein Container von Basisobjekten, oder? Der ganze Sinn eines Containers mit Pointern auf Base
-Objekte besteht darin, dass Sie über sie hinweg iterieren und sie alle als Base
-Objekte behandeln können. Wenn Sie dynamic_cast
für einen% Derived
-Typ verwenden müssen, dann haben Sie das polymorphe Verhalten von Base*
s missbraucht.
Wenn Derived
von Base
erbt, dann ist Derived
is-a Base
. Einfach gesagt, wenn Sie einen polymorphen Base
-Zeiger auf einen Derived
-Typ verwenden, dann sollten Sie nur die Funktionen von Derived
verwenden, die es zu einem Base
machen.
Tags und Links c++ oop dynamic-cast