Wie implementiere ich die Vererbung von RealNumber und ComplexNumber?

11

Hoffentlich nicht zu akademisch ...

Angenommen, ich benötige reelle und komplexe Zahlen in meiner SW-Bibliothek.

Basierend auf der is-a- Beziehung (oder hier ) ist die reelle Zahl eine komplexe Zahl, wobei b im Imaginärteil der komplexen Zahl einfach 0 ist.

Auf der anderen Seite wäre meine Implementierung, dass das Kind das Elternteil erweitert, so dass ich in der übergeordneten RealNumber einen Realteil hätte und das Kind ComplexNumber imaginäre Kunst hinzufügen würde.

Es gibt auch eine Meinung, dass Vererbung böse ist .

Ich erinnere mich wie gestern, als ich an der Universität OOP lernte, mein Professor sagte, dies sei kein gutes Beispiel für Vererbung, da der absolute Wert dieser beiden unterschiedlich berechnet wird (aber dafür haben wir Methodenüberladung / Polymorfismus, oder?). .

Ich habe die Erfahrung gemacht, dass wir häufig Vererbung verwenden, um DRY zu lösen. Daher haben wir häufig künstliche abstrakte Klassen in der Hierarchie (wir haben oft Probleme, Namen zu finden, da sie keine Objekte aus einer realen Welt darstellen).

Betlista
quelle
7
Dies sieht aus wie in der vorherigen Frage behandelt: Sollte das Rechteck vom Quadrat erben?
Mücke
1
@gnat Oh man, das war ein weiteres Beispiel, das ich verwenden wollte ... Danke!
Betlista
7
... Beachten Sie, dass der Satz "reelle Zahl ist eine komplexe Zahl" im mathematischen Sinne nur für unveränderliche Zahlen gilt. Wenn Sie also unveränderliche Objekte verwenden, können Sie die LSP-Verletzung vermeiden (dies gilt auch für Quadrate und Rechtecke) SO antworte ).
Doc Brown
5
... Beachten Sie weiter, dass die Absolutwertberechnung für komplexe Zahlen auch für reelle Zahlen funktioniert, daher bin ich mir nicht sicher, was Ihr Professor gemeint hat. Wenn Sie eine "Abs ()" - Methode korrekt in einer unveränderlichen komplexen Zahl implementieren und daraus ein "Real" ableiten, liefert die Abs () -Methode weiterhin korrekte Ergebnisse.
Doc Brown
3
Mögliches Duplikat von Sollte das Rechteck vom Quadrat erben?
BobDalgleish

Antworten:

17

Auch wenn eine reelle Zahl im mathematischen Sinne eine komplexe Zahl ist, ist es keine gute Idee, reelle aus komplexen Zahlen abzuleiten. Es verstößt gegen das Liskov-Substitutionsprinzip und besagt (unter anderem), dass eine abgeleitete Klasse die Eigenschaften einer Basisklasse nicht verbergen sollte.

In diesem Fall müsste eine reelle Zahl den Imaginärteil der komplexen Zahl verbergen. Es ist klar, dass es keinen Sinn macht, eine versteckte Gleitkommazahl (Imaginärteil) zu speichern, wenn Sie nur den Realteil benötigen.

Dies ist im Grunde das gleiche Problem wie das in einem Kommentar erwähnte Rechteck / Quadrat-Beispiel.

Frank Puffer
quelle
2
Heute habe ich dieses "Liskow-Substitutionsprinzip" mehrmals gesehen, ich muss mehr darüber lesen, weil ich das nicht weiß.
Betlista
7
Es ist vollkommen in Ordnung, den Imaginärteil einer reellen Zahl als Null zu melden, z. B. durch eine schreibgeschützte Methode. Es macht jedoch keinen Sinn, eine reelle Zahl als komplexe Zahl zu implementieren, bei der der Imaginärteil auf Null gesetzt wird. Dies ist genau ein Fall, in dem die Vererbung irreführend ist: Während die Vererbung der Schnittstelle hier wohl in Ordnung wäre, würde die Vererbung der Implementierung zu einem problematischen Design führen.
Amon
4
Es ist durchaus sinnvoll, reelle Zahlen von komplexen Zahlen erben zu lassen, solange beide unveränderlich sind. Und der Overhead macht dir nichts aus.
Deduplikator
@Deduplicator: Interessanter Punkt. Unveränderlichkeit löst viele Probleme, aber ich bin in diesem Fall noch nicht ganz überzeugt. Muss darüber nachdenken.
Frank Puffer
3

Kein gutes Beispiel für Vererbung, da der absolute Wert dieser beiden Werte unterschiedlich berechnet wird

Dies ist eigentlich kein zwingender Grund gegen jede Vererbung, sondern nur das vorgeschlagene class RealNumber<-> class ComplexNumberModell.

Sie könnten vernünftigerweise eine Schnittstelle definieren Number, die sowohl implementiert RealNumberals ComplexNumberauch implementiert wird.

Das könnte so aussehen

interface Number
{
    Number Add(Number rhs);
    Number Subtract(Number rhs);
    // ... etc
}

Aber dann möchten Sie die anderen NumberParameter in diesen Operationen auf denselben abgeleiteten Typ wie beschränken this, mit dem Sie sich nähern können

interface Number<T>
{
    Number<T> Add(Number<T> rhs);
    Number<T> Subtract(Number<T> rhs);
    // ... etc
}

Oder Sie würden stattdessen eine Sprache verwenden, die strukturellen Polymorphismus anstelle von Subtyp-Polymorphismus zulässt. Für den speziellen Fall von Zahlen benötigen Sie möglicherweise nur die Fähigkeit, arithmetische Operatoren zu überladen.

complex operator + (complex lhs, complex rhs);
complex operator - (complex lhs, complex rhs);
// ... etc

Number frobnicate<Number>(List<Number> foos, Number bar); // uses arithmetic operations
Caleth
quelle
0

Lösung: Haben Sie keine öffentliche RealNumberKlasse

Ich würde es völlig in Ordnung finden, wenn ComplexNumberes eine statische Factory-Methode fromDouble(double)gäbe, die eine komplexe Zahl mit einer imaginären Null zurückgibt. Sie können dann alle Vorgänge verwenden, die Sie für eine RealNumberInstanz dieser ComplexNumberInstanz verwenden würden.

Aber ich habe Probleme zu verstehen, warum Sie eine öffentlich geerbte RealNumberKlasse haben möchten / müssen . Normalerweise wird die Vererbung aus diesen Gründen verwendet (aus meinem Kopf heraus korrigieren Sie mich, wenn Sie etwas verpasst haben)

  • Erweiterung des Verhaltens. RealNumberskann keine zusätzlichen Operationen ausführen komplexe Zahlen können nicht ausführen, daher macht es keinen Sinn, dies zu tun.

  • Implementieren von abstraktem Verhalten mit einer bestimmten Implementierung. Da dies ComplexNumbernicht abstrakt sein sollte, gilt dies auch nicht.

  • Wiederverwendung von Code. Wenn Sie nur die ComplexNumberKlasse verwenden, verwenden Sie 100% des Codes wieder.

  • spezifischere / effizientere / genauere Implementierung für eine bestimmte Aufgabe. Dies könnte hier angewendet werden, RealNumberskönnte einige Funktionen schneller implementieren. Aber dann sollte diese Unterklasse hinter der Statik verborgen sein fromDouble(double)und außerhalb nicht bekannt sein. Auf diese Weise müsste der Imaginärteil nicht ausgeblendet werden. Für die Außenseite sollte es nur komplexe Zahlen geben (welche reellen Zahlen sind). Sie können diese private RealNumber-Klasse auch von allen Operationen in der komplexen Zahlenklasse zurückgeben, die zu einer reellen Zahl führen. (Dies setzt voraus, dass die Klassen wie die meisten Zahlenklassen unveränderlich sind.)

Es ist so, als würde man eine Unterklasse von Integer implementieren, die Zero heißt, und einige der Operationen fest codieren, da sie für Zero trivial sind. Sie können dies tun, da jede Null eine Ganzzahl ist, aber nicht öffentlich machen, sondern hinter einer Factory-Methode verstecken.

findusl
quelle
Ich bin nicht überrascht, eine Ablehnung zu bekommen, da ich keine Quelle habe, die ich beweisen könnte. Auch wenn sonst niemand eine Idee hatte, vermute ich immer, dass es einen Grund dafür geben könnte. Aber bitte sag mir, warum du denkst, dass es falsch ist und wie du es besser machen würdest.
Findusl
0

Zu sagen, dass eine reelle Zahl eine komplexe Zahl ist, hat in der Mathematik, insbesondere in der Mengenlehre, mehr Bedeutung als in der Informatik.
In der Mathematik sagen wir:

  • Eine reelle Zahl ist eine komplexe Zahl, da die Menge der komplexen Zahlen die Menge der reellen Zahlen enthält.
  • Eine rationale Zahl ist eine reelle Zahl, da die Menge der reellen Zahlen die Menge der rationalen Zahlen (und die Menge der irrationalen Zahlen) enthält.
  • Eine Ganzzahl ist eine rationale Zahl, da die Menge der rationalen Zahlen die Menge der Ganzzahlen enthält.

Dies bedeutet jedoch nicht, dass Sie beim Entwerfen Ihrer Bibliothek die Vererbung verwenden müssen oder sollten, um eine RealNumber- und eine ComplexNumber-Klasse einzuschließen. In Effective Java, Zweite Ausgabe von Joshua Bloch; Punkt 16 lautet "Komposition gegenüber Vererbung bevorzugen". Um die in diesem Element genannten Probleme zu vermeiden, kann Ihre RealNumber-Klasse nach der Definition in Ihrer ComplexNumber-Klasse verwendet werden:

public class ComplexNumber {
    private RealNumber realPart;
    private RealNumber imaginaryPart;

    // Implementation details are for you to write
}

Auf diese Weise können Sie Ihre RealNumber-Klasse wiederverwenden, um Ihren Code trocken zu halten und gleichzeitig die von Joshua Bloch identifizierten Probleme zu vermeiden.

Craig Noah
quelle
0

Hier gibt es zwei Probleme. Das erste ist, dass es üblich ist, dieselben Begriffe für die Arten von Containern und deren Inhaltstypen zu verwenden, insbesondere bei primitiven Typen wie Zahlen. Der Begriff doublewird beispielsweise verwendet, um sowohl einen Gleitkommawert mit doppelter Genauigkeit als auch einen Container zu beschreiben, in dem einer gespeichert werden kann.

Das zweite Problem ist, dass sich Beziehungen zwischen Containern, aus denen verschiedene Objekttypen gelesen werden können, genauso verhalten wie die Beziehungen zwischen den Objekten selbst, während sich Beziehungen zwischen Containern, in die verschiedene Objekttypen eingefügt werden können, gegenüber denen zwischen ihren Inhalten verhalten . Jeder Käfig, von dem bekannt ist, dass er eine Instanz von enthält Cat, ist ein Käfig, der eine Instanz von enthält Animal, muss aber kein Käfig sein, der eine Instanz von enthält SiameseCat. Andererseits ist jeder Käfig, der alle Instanzen von aufnehmen Catkann, ein Käfig, der alle Instanzen von aufnehmen kann SiameseCat, muss aber kein Käfig sein, der alle Instanzen von aufnehmen kann Animal. Die einzige Art von Käfig, die alle Instanzen von aufnehmen Catkann und garantiert werden kann, enthält niemals etwas anderes als eine Instanz vonCatist ein Käfig von Cat. Jede andere Art von Käfig wäre entweder nicht in der Lage, einige Fälle zu Catakzeptieren, die sie akzeptieren sollte, oder wäre in der Lage, Dinge zu akzeptieren, die keine Fälle sind Cat.

Superkatze
quelle