Zwei Strukturen mit denselben Elementen, aber unterschiedlichen Namen. Ist das eine gute Idee?


49

Ich schreibe ein Programm, bei dem sowohl mit polaren als auch mit kartesischen Koordinaten gearbeitet wird.

Ist es sinnvoll, zwei verschiedene Strukturen für jede Art von Punkten zu erstellen, eine mit Xund YMitgliedern und eine mit Rund ThetaMitgliedern.

Oder ist es zu viel und es ist besser, nur eine Struktur mit firstund secondals Mitglieder zu haben.

Was ich schreibe, ist einfach und es wird sich nicht viel ändern. Aber ich bin gespannt, was aus gestalterischer Sicht besser ist.

Ich denke, die erste Option ist besser. Es scheint lesbarer zu sein und ich werde den Vorteil der Typprüfung erhalten.


11
Ich erstelle immer eine neue Struktur / Klasse pro Zweck. Benötigen Sie einen 3D-Vektor, erstellen Sie einen struct 3d_vector mit drei Floats. Benötigen Sie eine UVW-Darstellung, erstellen Sie eine struct texture_coords mit drei Floats. Benötigen Sie eine Position in einer 3D-Umgebung, erstellen Sie eine Strukturposition mit drei Floats. Du verstehst den Punkt. Es ermöglicht eine weitaus bessere Lesbarkeit, als dasselbe überall zu verwenden. Wenn Sie die Mühe haben, dasselbe mehrmals zu definieren, verwenden Sie eine Basis-3-Float-Struktur und definieren Sie mehrere Namen als dieselbe Struktur.
Kevin

Wenn sie einige gemeinsame Methoden haben, dann vielleicht eine. Würdest du jemals die Gleichheit der beiden vergleichen müssen?
Paparazzo

8
@Sidney Wenn du die Funktionalität nicht unbedingt brauchst, würde ich das nicht tun. Sie müssen eine sin / arcsin-Operation ausführen, um zwischen den beiden Darstellungen zu konvertieren. Dies führt bei jeder Konvertierung ein bisschen Bitrot in den niedrigstwertigen Bits ein. Ich bin mir fast sicher, dass Sie ähnliche Schmerzen haben werden wie bei dem Versuch, mit einer Klasse umzugehen, die sowohl eine Ereignisfrequenz als auch eine Zeit zwischen Ereignissen (x und 1 / x) für eine Darstellung von etwas liefert. Zu verfolgen, welche Darstellung in der Klasse kanonisch ist, und mit all den runden Kopfschmerzen umzugehen, ist etwas, das ich nicht noch einmal tun möchte.
Dan Neely

3
Ein gutes Beispiel für einen Datentyp, der viele Dinge darstellen kann, ist der String, aber "stringy typed" ist ein Antipattern. Schreibe dein Beispiel. Versuchen Sie, das Skalarprodukt für einen Typ zu implementieren, der beide Koordinatensysteme unterstützt.
Nathan Cooper

1
Sie sollten nach Möglichkeit unterschiedliche Typen verwenden, um die Vorteile der Typensicherheit zu nutzen. Dadurch wird verhindert, dass Sie einen falschen Typ an eine Funktion senden (abhängig von Ihrer Programmiersprache, indem Sie die Richtigkeit der Kompilierungszeit überprüfen). Dies erleichtert die Wartung, da Sie alle Verwendungszwecke eines bestimmten Typs finden können (ohne dass es zu fehlerhaften Verwendungen aufgrund von Typenkonflikten kommt).
Erik Eidt

Antworten:


17

Ich habe beide Lösungen gesehen, es ist also definitiv kontextabhängig.

Aus Gründen der Lesbarkeit ist es sehr effektiv, mehrere Strukturen zu haben, wie Sie vorschlagen. In einigen Umgebungen möchten Sie jedoch häufig Änderungen an diesen Strukturen vornehmen und dabei feststellen, dass Sie Code duplizieren, z. B. Matrix-Vektor-Operationen. Es kann frustrierend werden, wenn die jeweilige Operation in Ihrem Vektor nicht verfügbar ist, weil sie dort nicht portiert wurde.

Die extreme Lösung (auf die wir schließlich eingegangen sind) besteht darin, eine CRTP-Basisklasse mit den Funktionen get <0> () get <1> () und get <2> () zu haben, um die Elemente auf generische Weise abzurufen. Diese Funktionen werden dann in der kartesischen oder polaren Struktur definiert, die von dieser Basisklasse abgeleitet ist. Es löst alle Probleme, hat aber einen ziemlich dummen Preis: Sie müssen die Template-Metaprogrammierung erlernen. Wenn die Metaprogrammierung von Vorlagen jedoch für Ihr Projekt bereits ein faires Spiel ist, ist dies möglicherweise eine gute Übereinstimmung.


1
Ihre Antwort ist sehr interessant. Kann ich ein Beispiel geben?
Moha das allmächtige Kamel

1
Ich kann ein Beispiel untersuchen, was ich getan habe: FIR-Filterung von Polaren, Kartesiern und Vektoren davon. Die Mathematik war ziemlich ähnlich, ohne Umbruch, der Code hatte aus Performancegründen sowieso einige Teile dupliziert und wir haben Vorlagen verwendet, wo es dasselbe war. Benutzte unterschiedliche Namen für alle Sachen. Corts "extreme Lösung" hätte ein paar Duplikate sparen können, aber nicht annähernd alle.
Eugene Ryabtsev

1
Meine erste Reaktion war, dass eine Situation wie diese besser durch Casting gelöst werden könnte, aber sie stellt sich als etwas riskant heraus .
200_success

114

Ja, das macht sehr viel Sinn.

Der Wert einer Struktur besteht nicht nur darin, dass sie Daten unter einem handlichen Namen kapselt. Der Wert ist, dass er Ihre Absichten kodiert, sodass der Compiler Sie dabei unterstützen kann, sicherzustellen, dass Sie sie eines Tages nicht verletzen (z. B. Verwechseln eines Polarkoordinatensatzes mit einem kartesischen Koordinatensatz).

Die Leute sind schlecht darin, sich an solche verworrenen Details zu erinnern, aber gut darin, mutige, erfinderische Pläne zu entwickeln. Computer sind gut darin, Details auszunageln, und schlecht darin, kreative Pläne zu machen. Aus diesem Grund ist es immer eine gute Idee, so viel Detailpflege wie möglich auf den Computer zu verlagern.


6
+1 Eine perfekte Beschreibung der Verwendung des Computers, um das zu tun, was Computer gut können, und um Ihr Gehirn auf Ihren eigenen Job konzentrieren zu lassen.
BrianH

8
"Programme sollten so geschrieben sein, dass sie gelesen werden können, und nur gelegentlich, dass Maschinen ausgeführt werden können." - aus "Struktur und Interpretation von Computerprogrammen" von Abelson und Sussman.
hlovdal

18

Ja, obwohl sowohl kartesisch als auch polar (an ihrer Stelle) äußerst vernünftige Koordinatendarstellungsschemata sind, sollten sie idealerweise niemals gemischt werden (wenn Sie einen kartesischen Punkt {1,1} haben, ist dies ein ganz anderer Punkt als polar {1,1) }).

Je nach Bedarf kann es auch sinnvoll sein eine Schnittstelle Koordinaten Umsetzung mit Methoden wie X(), Y(), Displacement()und Angle()(oder möglicherweise Radius()und Theta()je nach).


Noch wichtiger ist es, wenn das OP Klassen aus diesen Strukturen erstellt, da die Operationen für kartesische und polare Koordinaten unterschiedlich sind.
Mindwin

1
+1 für den letzten Absatz, das ist die ideale Lösung. Ein Punkt ist Raum ist ein Objekt; Die interne Darstellung dieses Punktes sollte keine Rolle spielen. Natürlich können reale Bedenken (Leistung, Rundungsfehler) dazu führen, dass es wichtig wird. Es hängt alles davon ab, wofür dies verwendet wird.
BlueRaja - Danny Pflughoeft

Auch für dieses Beispiel ist es unwahrscheinlich, dass sich etwas ändert. Wenn es sich jedoch um zwei andere Klassen handelt, sagt Ihnen nichts, dass sie an einigen Punkten voneinander abweichen könnten.
Farbstoffe

8

Letztendlich besteht das Ziel der Programmierung darin, die Transistor-Bits umzuschalten, um nützliche Arbeit zu leisten. Wenn Sie jedoch auf einem so niedrigen Niveau denken, kann dies zu einer unüberschaubaren Verrücktheit führen. Aus diesem Grund gibt es höhere Programmiersprachen, die Ihnen helfen, die Komplexität zu verbergen.

Wenn Sie nur eine Struktur mit den Elementen named firstund erstellen second, haben die Namen keine Bedeutung. Sie würden sie im Wesentlichen als Speicheradressen behandeln. Das macht den Zweck der höheren Programmiersprache zunichte.

Nur weil sie alle darstellbar sind, doublebedeutet dies nicht, dass Sie sie austauschbar verwenden können. Beispielsweise ist θ ein dimensionsloser Winkel, während y Längeneinheiten hat. Da die Typen nicht logisch ersetzbar sind, sollten es zwei inkompatible Strukturen sein.

Wenn Sie wirklich Tricks zur Wiederverwendung des Gedächtnisses spielen müssen - und das sollten Sie mit ziemlicher Sicherheit nicht -, können Sie einen unionTyp in C verwenden, um Ihre Absicht zu verdeutlichen.


Beste Antwort, IMHO
Dean Radcliffe

2

Erstens, haben beide explizit, wie nach @ Kilian-foths ganz vernünftiger Antwort.

Ich möchte jedoch hinzufügen:

Fragen Sie: Haben Sie wirklich Operationen, die für beide generisch sind, wenn sie als Paar betrachtet werden double? Beachten Sie dies nicht gleichbedeutend mit der Aussage, dass Sie Vorgänge haben, die für beide in ihren eigenen Bedingungen gelten. Zum Beispiel interessiert 'plot (Coord)', ob Coordpolar oder kartesisch ist. Auf der anderen Seite werden die Daten so behandelt, wie sie sind, wenn Sie in der Datei bleiben. Wenn Sie wirklich generische Operationen verfügen, erwägen die Definition entweder eine Basisklasse, oder definieren einen Konverter ein std::pair<double, double>oder tupleoder was auch immer Sie in Ihrer Sprache haben.

Ferner könnte ein Ansatz darin bestehen, einen Koordinatentyp als grundlegender und den anderen als bloße Unterstützung für Benutzer- oder externe Interaktion zu behandeln.

Sie können also sicherstellen, dass alle grundlegenden Vorgänge für codiert sind, Cartesianund dann die Konvertierung Polarnach unterstützen Cartesian. Dadurch wird vermieden, dass verschiedene Versionen vieler Vorgänge implementiert werden.


1

Eine mögliche Lösung ist, abhängig von der Sprache und wenn Sie wissen, dass beide Klassen ähnliche Methoden und Operationen haben, die einmalige Definition der Klasse und die Verwendung von Typ-Aliasen, um die Typen explizit unterschiedlich zu benennen.

Dies hat auch den Vorteil, dass Sie, solange die Klassen exakt gleich sind, nur eine beibehalten können. Sobald Sie sie ändern müssen, müssen Sie den Code nicht mehr mit ihnen ändern, da die Typen bereits verwendet wurden deutlich.

Eine weitere Option, die wiederum von der Verwendung der Klassen abhängt (wenn Sie Polymorphismus und dergleichen benötigen), ist die Verwendung der öffentlichen Vererbung für beide neuen Typen, sodass sie dieselbe öffentliche Schnittstelle haben wie der gemeinsame Typ, den sie beide darstellen. Dies ermöglicht auch eine getrennte Entwicklung der Typen.


Die Namen der Mitglieder in beiden Klassen sind nicht gleich. Eigentlich sind die Namen der einzige Unterschied zwischen den beiden Klassen
Moha das allmächtige Kamel

@ Mhd.Tahawi Sie können Getter und Setter mit den entsprechenden Namen implementieren und dabei sicherstellen, dass die von Ihnen verwendete Klasse dieselbe ist, jedoch die entsprechenden Namen für die Operationen bereitstellen, die Sie verwenden möchten. Es wird etwas ausführlicher, aber Sie müssen weniger Code duplizieren.
Svalorzen

0

Ich halte es in diesem Fall für eine schlechte Idee, dieselben Mitgliedsnamen zu haben, da der Code dadurch fehleranfälliger wird.

Stellen Sie sich das Szenario vor: Sie haben ein paar kartesische Punkte: pntA und pntB. Dann entscheiden Sie sich aus irgendeinem Grund, dass sie besser in Polarkoordinaten dargestellt werden sollen, und ändern die Deklaration und den Konstruktor.

Wenn alle Ihre Operationen nur Methodenaufrufe wären wie:

double distance = pntA.distanceFrom(pntB);

dann geht es dir gut Aber was ist, wenn Sie die Mitglieder explizit verwendet haben? Vergleichen Sie

double leftMargin = abs(pntA.x - pntB.x);
double leftMargin = abs(pntA.first - pntB.first);

Im ersten Fall wird der Code nicht kompiliert. Sie sehen den Fehler sofort und können ihn beheben. Wenn Sie jedoch dieselben Mitgliedsnamen haben, liegt der Fehler nur auf der logischen Ebene und ist viel schwerer zu erkennen.

Wenn Sie in einer nicht objektorientierten Sprache schreiben, ist es noch einfacher, eine falsche Struktur an die Funktion zu übergeben. Was hindert Sie daran, den folgenden Code zu schreiben?

double distance = calculate_distance_polar(cartesianPointA, polarPointB);

Verschiedene Datentypen ermöglichen es Ihnen andererseits, den Fehler während der Kompilierung zu finden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.