Sollte ich mein Design ändern, um dynamische Umwandlungen zu verhindern?

8

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).

    
Double Dan 21.03.2013, 15:12
quelle

6 Antworten

8

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.

    
Jerry Coffin 21.03.2013 15:22
quelle
0

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.

    
Brian Agnew 21.03.2013 15:16
quelle
0

Die Frage ist ziemlich weit gefasst, also einige allgemeine Punkte zu beachten:

  • Es ist ein Merkmal der Sprache aus einem bestimmten Grund, was bedeutet, dass es gültige Anwendungsfälle gibt.
  • Es ist generell nicht gut oder schlecht.
  • Überprüfen Sie, ob es die richtige Lösung für Ihr Problem ist.
  • Kennen Sie die Alternativen , um Ihr Problem zu lösen.
  • Vergleiche mit anderen Sprachen sollten die Frage enthalten, ob die Alternativen von C ++ / C ++ 11 auch in dieser anderen Sprache möglich sind.
  • dynamic_cast ergibt bestimmte Kosten. Wenn es sich um die Laufzeitleistung handelt, überprüfen Sie die Kosten der Alternativen.
  • Überprüfungen werden zur Laufzeit ausgeführt, was zu einem gewissen Risiko führt, dass Sie fehlerhafte Software liefern, wenn sie nicht ordnungsgemäß getestet wird. Mit der statischen Typprüfung kann Ihnen der Compiler helfen, selbst garantieren , dass bestimmte Probleme / Fehler nicht auftreten.
Daniel Frey 21.03.2013 15:19
quelle
0

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.

    
SomeWittyUsername 21.03.2013 15:20
quelle
0

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.

    
Joel Falcou 21.03.2013 15:23
quelle
0

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.

    
Joseph Mansfield 21.03.2013 15:25
quelle

Tags und Links