Was ist eine gute Entwurfspraxis, um nicht nach einem Unterklassentyp zu fragen?

11

Ich habe gelesen, dass, wenn Ihr Programm wissen muss, um welche Klasse es sich bei einem Objekt handelt, dies normalerweise auf einen Konstruktionsfehler hinweist. Daher möchte ich wissen, was eine gute Vorgehensweise ist, um damit umzugehen. Ich implementiere eine Klassenform mit verschiedenen von ihr geerbten Unterklassen wie Kreis, Polygon oder Rechteck und habe verschiedene Algorithmen, um festzustellen, ob ein Kreis mit einem Polygon oder einem Rechteck kollidiert. Nehmen wir dann an, wir haben zwei Instanzen von Shape und möchten wissen, ob eine mit der anderen kollidiert. Bei dieser Methode muss ich ableiten, welcher Unterklassentyp das Objekt ist, mit dem ich kollidiere, um zu wissen, welchen Algorithmus ich aufrufen soll, aber dies ist ein schlechtes Design oder schlechte Praxis? So habe ich es gelöst.

abstract class Shape {
  ShapeType getType();
  bool collide(Shape other);
}

class Circle : Shape {
  getType() { return Type.Circle; }

  bool collide(Shape other) {
    if(other.getType() == Type.Rect) {
      collideCircleRect(this, (Rect) other);     
    } else if(other.getType() == Type.Polygon) {
      collideCirclePolygon(this, (Polygon) other);
    }
  }
}

Dies ist ein schlechtes Designmuster? Wie könnte ich das lösen, ohne auf die Unterklassentypen schließen zu müssen?

Alejandro
quelle
1
Am Ende kennt jede Instanz, z. B. Circle, alle anderen Formtypen. Sie sind also alle irgendwie fest miteinander verbunden. Und sobald Sie eine neue Form wie Triangle hinzufügen, wird die Unterstützung für Triangles überall hinzugefügt. Es hängt davon ab, was Sie öfter ändern möchten. Werden Sie neue Formen hinzufügen, ist dieses Design schlecht. Weil Sie eine Lösung haben - Ihre Unterstützung für Dreiecke muss überall hinzugefügt werden. Stattdessen sollten Sie Ihre Kollisionserkennung in eine separate Klasse extrahieren, die mit allen Typen und Delegaten arbeiten kann.
Thepacker
IMO kommt es auf die Leistungsanforderungen an. Je spezifischer der Code ist, desto optimierter kann er sein und desto schneller wird er ausgeführt. In diesem speziellen Fall (auch implementiert) ist die Überprüfung des Typs in Ordnung, da maßgeschneiderte Kollisionsprüfungen enorm schneller sein können als eine generische Lösung. Aber wenn die Laufzeitleistung nicht kritisch ist, würde ich immer den allgemeinen / polymorphen Ansatz wählen.
Marstato
Vielen Dank an alle, in meinem Fall ist die Leistung entscheidend und ich werde keine neuen Formen hinzufügen. Vielleicht mache ich den CollisionDetection-Ansatz. Allerdings musste ich immer noch den Typ der Unterklasse kennen, sollte ich eine "Type getType ()" - Methode beibehalten Shape oder stattdessen eine Art "Instanz von" mit Shape in der CollisionDetection-Klasse?
Alejandro
1
Es gibt kein effektives Kollisionsverfahren zwischen abstrakten ShapeObjekten. Ihre Logik hängt von den Interna anderer Objekte ab, es sei denn, Sie überprüfen die Kollision auf Grenzpunkte bool collide(x, y)(eine Teilmenge der Kontrollpunkte kann ein guter Kompromiss sein). Andernfalls müssen Sie den Typ irgendwie überprüfen. Wenn wirklich Abstraktionen erforderlich sind, sollte die Erstellung von CollisionTypen (für Objekte im Bereich des aktuellen Schauspielers) der richtige Ansatz sein.
Schauder

Antworten:

13

Polymorphismus

Solange Sie getType()oder etwas Ähnliches verwenden, verwenden Sie keinen Polymorphismus.

Ich verstehe das Gefühl, dass Sie wissen müssen, welchen Typ Sie haben. Aber jede Arbeit, die Sie machen möchten, während Sie wissen, dass dies wirklich in die Klasse gedrängt werden sollte. Dann sagst du einfach, wann es geht.

Der Prozedurcode erhält Informationen und trifft dann Entscheidungen. Objektorientierter Code weist Objekte an, Dinge zu tun.
- Alec Sharp

Dieses Prinzip heißt erzählen, nicht fragen . Wenn Sie dem folgen, können Sie Details wie Typ nicht verbreiten und eine Logik erstellen, die auf sie einwirkt. Dadurch wird eine Klasse auf den Kopf gestellt. Es ist besser, dieses Verhalten in der Klasse beizubehalten, damit es sich ändern kann, wenn sich die Klasse ändert.

Verkapselung

Sie können mir sagen, dass niemals andere Formen benötigt werden, aber ich glaube Ihnen nicht und Sie sollten es auch nicht.

Ein netter Effekt der folgenden Kapselung ist, dass es einfach ist, neue Typen hinzuzufügen, da sich ihre Details nicht auf den Code verteilen, in dem sie angezeigt werden, ifund auf die switchLogik. Der Code für einen neuen Typ sollte sich alle an einer Stelle befinden.

Ein typunabhängiges Kollisionserkennungssystem

Lassen Sie mich Ihnen zeigen, wie ich ein Kollisionserkennungssystem entwerfen würde, das performant ist und mit jeder 2D-Form funktioniert, ohne sich um den Typ zu kümmern.

Geben Sie hier die Bildbeschreibung ein

Angenommen, Sie sollten das zeichnen. Scheint einfach. Es sind alles Kreise. Es ist verlockend, eine Kreisklasse zu erstellen, die Kollisionen versteht. Das Problem ist, dass wir dadurch eine Denkrichtung durchlaufen, die auseinanderfällt, wenn wir 1000 Kreise benötigen.

Wir sollten nicht an Kreise denken. Wir sollten über Pixel nachdenken.

Was wäre, wenn ich Ihnen sagen würde, dass Sie denselben Code, mit dem Sie diese Typen zeichnen, erkennen können, wenn sie sich berühren oder auf welchen der Benutzer klickt?

Geben Sie hier die Bildbeschreibung ein

Hier habe ich jeden Kreis mit einer einzigartigen Farbe gezeichnet (wenn Ihre Augen gut genug sind, um den schwarzen Umriss zu sehen, ignorieren Sie das einfach). Dies bedeutet, dass jedes Pixel in diesem versteckten Bild dem entspricht, was es gezeichnet hat. Eine Hashmap kümmert sich gut darum. Auf diese Weise können Sie tatsächlich Polymorphismus betreiben.

Dieses Bild müssen Sie dem Benutzer nie zeigen. Sie erstellen es mit demselben Code, der den ersten gezeichnet hat. Nur mit verschiedenen Farben.

Wenn der Benutzer auf einen Kreis klickt, weiß ich genau, welcher Kreis, weil nur ein Kreis diese Farbe hat.

Wenn ich einen Kreis über einen anderen zeichne, kann ich schnell jedes Pixel lesen, das ich überschreiben möchte, indem ich sie in einem Satz ablege. Wenn ich mit den Sollwerten für jeden Kreis fertig bin, mit dem er kollidiert ist, muss ich jeden nur noch einmal anrufen, um ihn über die Kollision zu informieren.

Ein neuer Typ: Rechtecke

Dies wurde alles mit Kreisen gemacht, aber ich frage Sie: Würde es mit Rechtecken anders funktionieren?

In das Erkennungssystem ist kein Kreiswissen gelangt. Radius, Umfang oder Mittelpunkt sind egal. Es kümmert sich um Pixel und Farbe.

Der einzige Teil dieses Kollisionssystems, der in die einzelnen Formen gedrückt werden muss, ist eine einzigartige Farbe. Ansonsten können die Formen nur daran denken, ihre Formen zu zeichnen. Darin sind sie sowieso gut.

Wenn Sie jetzt die Kollisionslogik schreiben, ist es Ihnen egal, welchen Subtyp Sie haben. Sie sagen, es soll kollidieren und es sagt Ihnen, was es unter der Form gefunden hat, die es vorgibt zu zeichnen. Typ muss nicht bekannt sein. Das bedeutet, dass Sie so viele Untertypen hinzufügen können, wie Sie möchten, ohne den Code in anderen Klassen aktualisieren zu müssen.

Implementierungsoptionen

Wirklich, es muss keine eindeutige Farbe sein. Dies können tatsächliche Objektreferenzen sein und eine Indirektionsebene speichern. Aber diese sehen in dieser Antwort nicht so gut aus.

Dies ist nur ein Implementierungsbeispiel. Es gibt sicherlich andere. Dies sollte zeigen, dass das gesamte System umso besser funktioniert, je näher Sie diese Formuntertypen an ihrer einzigen Verantwortung festhalten. Es gibt wahrscheinlich schnellere und weniger speicherintensive Lösungen, aber wenn sie mich dazu zwingen, das Wissen über die Subtypen zu verbreiten, würde ich es ablehnen, sie auch bei Leistungssteigerungen zu verwenden. Ich würde sie nicht benutzen, wenn ich sie nicht eindeutig brauchte.

Doppelversand

Bisher habe ich den Doppelversand völlig ignoriert . Ich habe das getan, weil ich konnte. Solange es der Kollisionslogik egal ist, welche beiden Typen kollidierten, brauchen Sie sie nicht. Wenn Sie es nicht brauchen, verwenden Sie es nicht. Wenn Sie glauben, dass Sie es brauchen könnten, verschieben Sie den Umgang damit so lange wie möglich. Diese Haltung nennt man YAGNI .

Wenn Sie entscheiden, dass Sie wirklich verschiedene Arten von Kollisionen benötigen, fragen Sie sich selbst, ob n-Form-Subtypen wirklich n 2 Arten von Kollisionen benötigen . Bisher habe ich sehr hart gearbeitet, um das Hinzufügen eines weiteren Formuntertyps zu vereinfachen. Ich möchte es nicht mit einer Doppelversand-Implementierung verderben , die Kreise dazu zwingt, zu wissen, dass Quadrate existieren.

Wie viele Arten von Kollisionen gibt es überhaupt? Ein wenig Spekulieren (eine gefährliche Sache) erfindet elastische Kollisionen (federnd), unelastisch (klebrig), energisch (explodieren) und destruktiv (schädlich). Es könnte mehr geben, aber wenn dies weniger als n 2 ist, können wir unsere Kollisionen nicht überdenken.

Das heißt, wenn mein Torpedo etwas trifft, das Schaden akzeptiert, muss er nicht wissen, dass er ein Raumschiff trifft. Es muss nur sagen: "Ha ha! Du hast 5 Schadenspunkte genommen."

Dinge, die Schaden verursachen, senden Schadensmeldungen an Dinge, die Schadensmeldungen annehmen. Auf diese Weise können Sie neue Formen hinzufügen, ohne die anderen Formen über die neue Form zu informieren. Sie verbreiten sich nur über neue Arten von Kollisionen.

Das Raumschiff kann zum Torp zurückschicken: "Ha ha! Du hast 100 Schadenspunkte genommen." sowie "Du steckst jetzt an meinem Rumpf fest". Und der Torp kann zurückschicken "Nun, ich bin fertig damit, mich zu vergessen".

Zu keinem Zeitpunkt weiß einer genau, was jeder ist. Sie wissen nur, wie sie über eine Kollisionsschnittstelle miteinander sprechen können.

Mit dem doppelten Versand können Sie die Dinge genauer steuern, aber möchten Sie das wirklich ?

Wenn Sie dies tun, denken Sie bitte zumindest darüber nach, einen doppelten Versand durch Abstraktionen darüber durchzuführen, welche Arten von Kollisionen eine Form akzeptiert, und nicht über die tatsächliche Formimplementierung. Kollisionsverhalten können Sie auch als Abhängigkeit einfügen und an diese Abhängigkeit delegieren.

Performance

Leistung ist immer kritisch. Das heißt aber nicht, dass es immer ein Problem ist. Testleistung. Spekulieren Sie nicht nur. Alles andere im Namen der Leistung zu opfern, führt normalerweise sowieso nicht zu Perforationscode.

candied_orange
quelle
Lassen Sie uns diese Diskussion im Chat fortsetzen .
candied_orange
+1 für "Sie können mir sagen, dass keine anderen Formen jemals benötigt werden, aber ich glaube Ihnen nicht und Sie sollten es auch nicht."
Tulains Córdova
Wenn Sie an Pixel denken, kommen Sie nicht weiter, wenn es in diesem Programm nicht um das Zeichnen von Formen geht, sondern um rein mathematische Berechnungen. Diese Antwort impliziert, dass Sie alles der wahrgenommenen objektorientierten Reinheit opfern sollten. Es enthält auch einen Widerspruch: Sie sagen zuerst, dass wir unser gesamtes Design auf der Idee basieren sollten, dass wir in Zukunft möglicherweise mehr Arten von Formen benötigen, dann sagen Sie "YAGNI". Schließlich vernachlässigen Sie, dass das Hinzufügen von Typen häufig das Hinzufügen von Operationen erschwert. Dies ist schlecht, wenn die Typhierarchie relativ stabil ist, sich die Operationen jedoch stark ändern.
Christian Hackl
7

Die Beschreibung des Problems klingt so, als ob Sie Multimethoden (auch als Mehrfachversand bezeichnet) verwenden sollten, in diesem speziellen Fall - Doppelversand . Die erste Antwort ging ausführlich auf den generischen Umgang mit kollidierenden Formen beim Raster-Rendering ein, aber ich glaube, OP wollte eine "Vektor" -Lösung, oder vielleicht wurde das gesamte Problem in Form von Formen neu formuliert, was ein klassisches Beispiel in OOP-Erklärungen ist.

Selbst der zitierte Wikipedia-Artikel verwendet dieselbe Kollisionsmetapher. Lassen Sie mich nur zitieren (Python verfügt nicht über integrierte Multimethoden wie einige andere Sprachen):

@multimethod(Asteroid, Asteroid)
def collide(a, b):
    """Behavior when asteroid hits asteroid"""
    # ...define new behavior...
@multimethod(Asteroid, Spaceship)
def collide(a, b):
    """Behavior when asteroid hits spaceship"""
    # ...define new behavior...
# ... define other multimethod rules ...

Die nächste Frage ist also, wie Sie Unterstützung für Multimethoden in Ihrer Programmiersprache erhalten.

Roman Susi
quelle
Ja, Sonderfall von Mehrfachversand alias Multimethods, der Antwort hinzugefügt
Roman Susi
5

Dieses Problem erfordert eine Neugestaltung auf zwei Ebenen.

Zunächst müssen Sie die Logik zum Erkennen der Kollision zwischen den Formen aus den Formen herausziehen. Auf diese Weise würden Sie OCP nicht jedes Mal verletzen, wenn Sie dem Modell eine neue Form hinzufügen müssen. Stellen Sie sich vor, Sie haben bereits Kreis, Quadrat und Rechteck definiert. Sie könnten es dann so machen:

class ShapeCollisionDetector
{
    public void DetectCollisionCircleCircle(Circle firstCircle, Circle secondCircle)
    { 
        //Code that detects collision between two circles
    }

    public void DetectCollisionCircleSquare(Circle circle, Square square)
    {
        //Code that detects collision between circle and square
    }

    public void DetectCollisionCircleRectangle(Circle circle, Rectangle rectangle)
    {
        //Code that detects collision between circle and rectangle
    }

    public void DetectCollisionSquareSquare(Square firstSquare, Square secondSquare)
    {
        //Code that detects collision between two squares
    }

    public void DetectCollisionSquareRectangle(Square square, Rectangle rectangle)
    {
        //Code that detects collision between square and rectangle
    }

    public void DetectCollisionRectangleRectangle(Rectangle firstRectangle, Rectangle secondRectangle)
    { 
        //Code that detects collision between two rectangles
    }
}

Als Nächstes müssen Sie festlegen, dass die entsprechende Methode abhängig von der Form aufgerufen wird, die sie aufruft. Sie können dies mithilfe von Polymorphismus und Besuchermuster tun . Um dies zu erreichen, müssen wir das entsprechende Objektmodell haben. Erstens müssen alle Formen an derselben Schnittstelle haften:

    interface IShape
{
    void DetectCollision(IShape shape);
    void Accept (ShapeVisitor visitor);
}

Als nächstes müssen wir eine übergeordnete Besucherklasse haben:

    abstract class ShapeVisitor
{
    protected ShapeCollisionDetector collisionDetector = new ShapeCollisionDetector();

    abstract public void VisitCircle (Circle circle);

    abstract public void VisitSquare(Square square);

    abstract public void VisitRectangle(Rectangle rectangle);

}

Ich verwende hier eine Klasse anstelle der Schnittstelle, da jedes Besucherobjekt ein Attribut vom ShapeCollisionDetectorTyp haben muss.

Jede Implementierung der IShapeSchnittstelle würde den entsprechenden Besucher instanziieren und die entsprechende AcceptMethode des Objekts aufrufen, mit dem das aufrufende Objekt interagiert, wie folgt:

    class Circle : IShape
{
    public void DetectCollision(IShape shape)
    {
        CircleVisitor visitor = new CircleVisitor(this);
        shape.Accept(visitor);
    }

    public void Accept(ShapeVisitor visitor)
    {
        visitor.VisitCircle(this);
    }
}

    class Rectangle : IShape
{
    public void DetectCollision(IShape shape)
    {
        RectangleVisitor visitor = new RectangleVisitor(this);
        shape.Accept(visitor);
    }

    public void Accept(ShapeVisitor visitor)
    {
        visitor.VisitRectangle(this);
    }
}

Und bestimmte Besucher würden so aussehen:

    class CircleVisitor : ShapeVisitor
{
    private Circle Circle { get; set; }

    public CircleVisitor(Circle circle)
    {
        this.Circle = circle;
    }

    public override void VisitCircle(Circle circle)
    {
        collisionDetector.DetectCollisionCircleCircle(Circle, circle);
    }

    public override void VisitSquare(Square square)
    {
        collisionDetector.DetectCollisionCircleSquare(Circle, square);
    }

    public override void VisitRectangle(Rectangle rectangle)
    {
        collisionDetector.DetectCollisionCircleRectangle(Circle, rectangle);
    }
}

    class RectangleVisitor : ShapeVisitor
{
    private Rectangle Rectangle { get; set; }

    public RectangleVisitor(Rectangle rectangle)
    {
        this.Rectangle = rectangle;
    }

    public override void VisitCircle(Circle circle)
    {
        collisionDetector.DetectCollisionCircleRectangle(circle, Rectangle);
    }

    public override void VisitSquare(Square square)
    {
        collisionDetector.DetectCollisionSquareRectangle(square, Rectangle);
    }

    public override void VisitRectangle(Rectangle rectangle)
    {
        collisionDetector.DetectCollisionRectangleRectangle(Rectangle, rectangle);
    }
}

Auf diese Weise müssen Sie die Formklassen nicht jedes Mal ändern, wenn Sie eine neue Form hinzufügen, und Sie müssen nicht nach dem Formtyp suchen, um die entsprechende Kollisionserkennungsmethode aufzurufen.

Ein Nachteil dieser Lösung besteht darin, dass Sie beim Hinzufügen einer neuen Form die ShapeVisitor-Klasse um die Methode für diese Form erweitern müssen (z. B. VisitTriangle(Triangle triangle)) und diese Methode folglich in allen anderen Besuchern implementieren müssen. Da dies jedoch eine Erweiterung ist, in dem Sinne, dass keine vorhandenen Methoden geändert werden, sondern nur neue hinzugefügt werden, verstößt dies nicht gegen OCP , und der Code-Overhead ist minimal. Durch die Verwendung der Klasse ShapeCollisionDetectorvermeiden Sie außerdem die Verletzung von SRP und Code-Redundanz.

Vladimir Stokic
quelle
5

Ihr Grundproblem besteht darin, dass in den meisten modernen OO-Programmiersprachen die Funktionsüberladung bei dynamischer Bindung nicht funktioniert (dh die Art der Funktionsargumente wird zur Kompilierungszeit bestimmt). Was Sie benötigen würden, ist ein virtueller Methodenaufruf, der für zwei Objekte und nicht nur für ein Objekt virtuell ist. Solche Methoden werden Multi-Methoden genannt . Es gibt jedoch Möglichkeiten, dieses Verhalten in Sprachen wie Java, C ++ usw. zu emulieren . Hier ist der doppelte Versand sehr praktisch.

Die Grundidee ist, dass Sie den Polymorphismus zweimal verwenden. Wenn zwei Formen kollidieren, können Sie die richtige Kollisionsmethode eines der Objekte durch Polymorphismus aufrufen und das andere Objekt des generischen Formtyps übergeben. In der aufgerufenen Methode wissen Sie dann, ob dieses Objekt ein Kreis, ein Rechteck oder was auch immer ist. Anschließend rufen Sie die Kollisionsmethode für das übergebene Formobjekt auf und übergeben es an dieses Objekt. Dieser zweite Aufruf findet dann durch Polymorphismus wieder den richtigen Objekttyp.

abstract class Shape {
  bool collide(Shape other);
  bool collide(Rect other);
  bool collide(Circle other);
}

class Circle : Shape {

  bool collide(Shape other) {
    return other.collide(this);
  }

  bool collide(Rect other) {
    // algorithm to detect collision between Circle and Rect
  }

  // ...
}

class Rect : Shape {

  bool collide(Shape other) {
    return other.collide(this);
  }

  bool collide(Circle other) {
    // algorithm to detect collision between Circle and Rect
  }

  // ...
}

Ein großer Nachteil dieser Technik ist jedoch, dass jede Klasse in der Hierarchie über alle Geschwister Bescheid wissen muss. Dies stellt einen hohen Wartungsaufwand dar, wenn später eine neue Form hinzugefügt wird.

Sigy
quelle
2

Vielleicht ist dies nicht der beste Weg, um dieses Problem anzugehen

Die mathematische Formkollision ist speziell für die Formkombinationen. Dies bedeutet, dass die Anzahl der Unterprogramme, die Sie benötigen, das Quadrat der Anzahl der Formen ist, die Ihr System unterstützt. Bei den Formkollisionen handelt es sich nicht um Operationen an Formen, sondern um Operationen, bei denen Formen als Parameter verwendet werden.

Überlastungsstrategie für Bediener

Wenn Sie das zugrunde liegende mathematische Problem nicht vereinfachen können, würde ich den Ansatz der Operatorüberladung empfehlen. Etwas wie:

 public final class ShapeOp 
 {
     static { ... }

     public static boolean collision( Shape s1, Shape s2 )  { ... }
     public static boolean collision( Point p1, Point p2 ) { ... }
     public static boolean collision( Point p1, Square s1 ) { ... }
     public static boolean collision( Point p1, Circle c1 ) { ... }
     public static boolean collision( Point p1, Line l1 ) { ... }
     public static boolean collision( Square s1, Point p2 ) { ... }
     public static boolean collision( Square s1, Square s2 ) { ... }
     public static boolean collision( Square s1, Circle c1 ) { ... }
     public static boolean collision( Square s1, Line l1 ) { ... }
     (...)

Auf dem statischen Intializer würde ich mithilfe der Reflexion eine Karte der Methoden erstellen, um einen diynamischen Dispather für die generische Kollisionsmethode (Shape s1, Shape s2) zu implementieren. Der statische Intializer kann auch eine Logik haben, um fehlende Kollisionsfunktionen zu erkennen und zu melden, ohne die Klasse zu laden.

Dies ähnelt der Überladung des C ++ - Operators. In C ++ ist die Überladung von Operatoren sehr verwirrend, da Sie einen festen Satz von Symbolen haben, die Sie überladen können. Das Konzept ist jedoch sehr interessant und kann mit statischen Funktionen repliziert werden.

Der Grund, warum ich diesen Ansatz verwenden würde, ist, dass Kollision keine Operation über einem Objekt ist. Eine Kollision ist eine externe Operation, die eine Beziehung zu zwei beliebigen Objekten besagt. Außerdem kann der statische Initialisierer prüfen, ob eine Kollisionsfunktion fehlt.

Vereinfachen Sie Ihr mathematisches Problem, wenn möglich

Wie bereits erwähnt, ist die Anzahl der Kollisionsfunktionen das Quadrat der Anzahl der Formtypen. Dies bedeutet, dass Sie in einem System mit nur 20 Formen 400 Routinen benötigen, mit 21 Formen 441 und so weiter. Dies ist nicht einfach zu erweitern.

Aber Sie können Ihre Mathematik vereinfachen . Anstatt die Kollisionsfunktion zu erweitern, können Sie jede Form rastern oder triangulieren. Auf diese Weise muss die Kollisionsmaschine nicht erweiterbar sein. Kollision, Entfernung, Schnittpunkt, Zusammenführen und verschiedene andere Funktionen sind universell.

Triangulieren

Haben Sie bemerkt, dass die meisten 3D-Pakete und Spiele alles triangulieren? Das ist eine der Formen der Vereinfachung der Mathematik. Dies gilt auch für 2D-Formen. Polys können trianguliert werden. Kreise und Splines können an Poligons angenähert werden.

Wieder ... haben Sie eine einzige Kollisionsfunktion. Ihre Klasse wird dann:

public class Shape 
{
    public Triangle[] triangulate();
}

Und Ihre Operationen:

public final class ShapeOp
{
    public static boolean collision( Triangle[] shape1, Triangle[] shape2 )
}

Einfacher ist es nicht?

Rasterisieren

Sie können Ihre Form rastern, um eine einzige Kollisionsfunktion zu erhalten.

Die Rasterung scheint eine radikale Lösung zu sein, kann jedoch erschwinglich und schnell sein, je nachdem, wie genau Ihre Formkollisionen sein müssen. Wenn sie nicht präzise sein müssen (wie in einem Spiel), haben Sie möglicherweise Bitmaps mit niedriger Auflösung. Die meisten Anwendungen erfordern keine absolute Genauigkeit in der Mathematik.

Annäherungen können gut genug sein. Der ANTON-Supercomputer für die Biologie-Simulation ist ein Beispiel. In seiner Mathematik werden viele schwer zu berechnende Quanteneffekte verworfen, und die bisher durchgeführten Simulationen stimmen mit den in der realen Welt durchgeführten Experimenten überein. Die in Game-Engines und Rendering-Paketen verwendeten PBR-Computergrafikmodelle vereinfachen die Computerleistung, die zum Rendern der einzelnen Frames erforderlich ist. Ist nicht wirklich physikalisch genau, aber nah genug, um mit bloßem Auge zu überzeugen.

Lucas
quelle