Vererbung: Rechteck und Quadtrat!?



  • BorisDieKlinge schrieb:

    Pinguin "ist ein" Vogel, trozt der fehlenden Eigenschaft "fliegen":)

    Was uns zu dem Schluss bringt, dass "fliegen" keine allgemeine Eigenschaft von Voegeln ist sondern nur von einer Unterklasse, den FlyingBirds, zu denen Pinguine nunmal nicht gehoeren (wie oben aber auch schon gesagt wurde).

    Was das Quadrat/Rechteck Problem angeht: Die Eigenschaft, dass beim Rechteck die Seiten unabhaengig voneinander sind, sind keine Verfeinerung, sondern eine Verallgemeinerung. Genauso wie die Eigenschaft, dass bei einem Fahrzeug im Allgemeinen die Zahl der Raeder nicht spezifiziert ist, bei einem Motorrad aber eben immer 2 ist.
    Die Erweiterung, die das Quadrat gegenueber dem Rechteck hat, ist die angesprochene Invariante, dass beide Kantenlaengen gleich sind. Die Settermethoden (so sie denn sinnvoll sind) muessten natuerlich diese Invariante garantieren, so dass setlength() und setHeight() jeweils beide Kantenlaengen veraendern muessten, wie man es eben erwartet, wenn ein Quadrat ein Quadrat bleiben soll.
    CStolls

    ASSERT(r.Width()==5 && r.Height()==10);
    

    bringt auch nicht die "Invariante" der unabhaengigen Seitenlaengen zum Ausdruck sondern verlangt konstante Seitenlaengen. Invarianten muessen, wie der Ausdruck schon sagt, immer erfuellt bleiben. Das obige ASSERT verhindert einzig die Aenderung der Kantenlaengen.

    Sprich: beim Nachmodellieren der wirklichen Welt muss man darauf achten, was ein Name wirklich bedeutet, und nicht was man sich landlaeufig darunter vorstellt. Ein Vogel ist nunmal zu allererst definiert durch seinen Biologischen Hintergrund, nicht durch die Eigenschaft, dass er ein flugfaehiges Wesen ist.



  • Ist zwar mit Ellipse und Kreis, kommt aber auf das gleiche heraus.
    http://www.parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.6

    Persönlich halte ich es für das beste zwei unabhängige Klassen zu definieren und eine implizite Konvertierung Quadrat => Rechteck und eine explizite anders herum welche auch fehl schlagen kann.

    Ein Rechteck kann man nicht immer als Quadrat ansehen daher ist ein Quadrat keine Basis für ein Rechteck. Ein Quadrat kann man immer als Rechteck ansehen jedoch sobald man anfängt ihn als Rechteck zu verändern ist es kein Quadrat mehr. Man verändert also den Type und kommt deswegen nicht ohne Konvertierung aus. Sobald man ein Quadrat als Rechteck betrachtet wird es zum Rechteck.

    Man könnte jedoch const Quadrat von const Rechteck ableiten. Davon würde ich aber auch im allgemein Fall abraten da die Konvertierung hier billiger wird als jede Vererbung.

    OT: Zum Thema Vögel die nicht fliegen können http://www.metacafe.com/watch/297785/kiwi/



  • pumuckl schrieb:

    CStolls

    ASSERT(r.Width()==5 && r.Height()==10);
    

    bringt auch nicht die "Invariante" der unabhaengigen Seitenlaengen zum Ausdruck sondern verlangt konstante Seitenlaengen. Invarianten muessen, wie der Ausdruck schon sagt, immer erfuellt bleiben. Das obige ASSERT verhindert einzig die Aenderung der Kantenlaengen.

    Was erwartest du denn, wenn du einem Rechteck die Seitenlängen 5 und 10 gibst? Die Invariante des Rechtecks ist, daß eine gesetzte Kantenlänge nicht durch irgendwelche unabhängigen Aufrufe verändert werden sollte (d.h. solange ich kein weiteres SetWidth() aufrufe, sollte die Breite auf dem gesetzten Wert bleiben).



  • CStoll schrieb:

    Die Invariante des Rechtecks ist, daß eine gesetzte Kantenlänge nicht durch irgendwelche unabhängigen Aufrufe verändert werden sollte

    JFTR, das ist keine Invariante. 🙂



  • Bashar schrieb:

    CStoll schrieb:

    Die Invariante des Rechtecks ist, daß eine gesetzte Kantenlänge nicht durch irgendwelche unabhängigen Aufrufe verändert werden sollte

    JFTR, das ist keine Invariante. 🙂

    Dann anders: wenn ich die Breite gesetzt habe und unmittelbar danach überprüfe, steht noch der von mir gesetzte Wert drin - andernfalls würde zumindest ich die Rechteck-Klasse in die Tonne treten (daß die Breite nach einigen Aufrufen von Hilfsfunktionen nicht mehr den ursprünglichen Wert haben muß, ist mir auch klar - aber ein SetHeight() hat absolut nichts mit der Breite zu tun).



  • wikipedia schrieb:

    Das liskovsche Substitutionsprinzip ist ein Kriterium in der objektorientierten Programmierung, das angibt, wann ein Datentyp als Unterklasse eines anderen Typs modelliert werden soll. Es wird von vielen als das einzig gültige Kriterium angesehen

    http://de.wikipedia.org/wiki/Liskovsches_Substitutionsprinzip



  • Bashar schrieb:

    CStoll schrieb:

    Die Invariante des Rechtecks ist, daß eine gesetzte Kantenlänge nicht durch irgendwelche unabhängigen Aufrufe verändert werden sollte

    JFTR, das ist keine Invariante. 🙂

    Bei allen "Just for the Records" und Spitzfindigkeiten sollte man imo nicht das Wesentliche (LSP, DBC) vergessen und das drückt CStolls Beitrag doch sehr gut aus.

    Die Klasse Quadrat hat eine Invariante, nämlich height == width. Gleichzeitig sollte *jede* Methode über eine Postcondition verfügen. Die einzig sinnvolle Postcondition für setWidth/setHeight ist, dass die Breite/Höhe nach dem Aufruf dem Wert des Arguments entspricht (von mir aus mit Precondition: arg > 0). Das wiederum ist aber unvereinbar mit der Invarianten eines Quadrats.

    Wenn man also Quadrat von Rechteck ableiten will, dann darf man entweder keine getrennten Funktionen für setHeight/setWidth bereitstellen oder man muss völlig künztliche Pre-/Postconditions definieren. Mache ich das nicht, dann kann Clientcode (und nur um den geht es bei öffentlicher Vererbung) nicht mehr typunabhängig implementiert werden.



  • Was Wikipedia unter obigem Link zum Thema schrieb:

    Zu beachten ist hierbei, dass die Entscheidung jeweils abhängig vom konkreten Fall ist. Ist beispielsweise eine Manipulation der geometrischen Figur nach der Erzeugung nicht vorgesehen, so kann Kreis durchaus von Ellipse abgeleitet sein: Dann ist "die Achsen können unabhängig voneinander skaliert werden" keine Eigenschaft der Klasse Ellipse, und somit muss sie auch keine Eigenschaft von Kreis sein, um Kreis zur Unterklasse von Ellipse zu machen.

    Damit waere dann auch schon wieder ne Menge gesagt.

    Zugegeben, mit HumeSikkins Argumenten macht es nicht unbedingt Sinn, ein Quadrat von einerm Rechteck abzuleiten, wenn denn das Rechteck die Settermethoden deklariert. Sollte sowas noetig sein, dann kann man es wieder wie mit dem Pinguin und dem Vogel halten: Man bilde eine Klasse mathRect , leite davon mathSquare und concreteRect ab sowie concreteSquare von mathSquare . Wobei die beiden Konkreten Klassen die Settermethoden etc. beinhalten, die math* Klassen jedoch nur den mathematischen Aspekt beinhalten, naemlich dass ein Rechteck ein Viereck mit 4 rechten Winkeln und 2 Kantenlaengen ist, und ein Quadrat wiederum ein Rechteck mit 2 gleichen Kantenlaengen ist.
    Ob die konkreten Klassen dann von den mathematischen ableiten oder diese nur beinhalten ist wieder ein anderes Thema.
    Ich bleibe jedoch dabei, dass man, wenn man nur die mathematischen Aspekte betrachtet, ein Quadrat durchaus von einem Rechteck ableiten kann. (Getter und Setter sind keine mathematischen Aspekte)



  • OK, darauf können wir uns einigen - bei immutablen Klassen macht die Verwandschaftsbeziehung Sinn (aber die meisten Programmierer arbeiten mit veränderbaren Rechtecken und Quadraten - und dann sollte man die Verwandschaft in Frage stellen).



  • Also die landläufige Meinung, das Vögel fliegen können, ist falsch.
    Ergo kann man Pinguin, Kiwi, Strauss, Emu, Amsel, Star und Fink von Vogel ableiten.

    Alle haben den selben Aufbau, sie haben Flügel, sie haben Federn.
    Selbst Pinguine, welche übrigens unter Wasser fliegen, mittels Flügelschlag.

    Und Quadrat vs. Rechteck ist imho auch leicht: Wozu eine Klasse Quadrat, wenn es nur ein Sonderfall des Rechtecks ist?
    Fürs was rechtfertigt sich hier eine eigene Klasse? (Abgesehen davon, das man eine Variable für die Breite sich einsparen kann, weil ein Quadrat gleich hoch und breit ist.)



  • phlox81 schrieb:

    Fürs was rechtfertigt sich hier eine eigene Klasse? (Abgesehen davon, das man eine Variable für die Breite sich einsparen kann, weil ein Quadrat gleich hoch und breit ist.)

    die invariante height==width



  • Warum ableiten? Was hat ein Quadrat für Funktionen die ein Rechteck nicht hat?



  • seh ich auch so schrieb:

    Warum ableiten? Was hat ein Quadrat für Funktionen die ein Rechteck nicht hat?

    Darum geht es nicht, der Punkt ist, dass man sich bei einer Ist-Ein-Beziehung auch ganz gut täuschen kann.



  • Jein. Das Problem ist, dass unsere gemeine Sprache nicht genau genug ist, so dass die Behauptung "Ein Quadrat ist ein Rechteck" wahr und falsch sein kann, je nachdem was wir unter Quadraten und Rechtecken verstehen. Auf der anderen Seite ist C++ sehr genau, so dass wir mit einem simplen

    class Quadrat : public Rechteck
    

    manchmal mehr sagen als wir eigentlich zu sagen glauben. Und daher ist dort Vorsicht gefragt.



  • "Ein Quadrat ist ein Rechteck" wahr und falsch sein kann

    Na von wegen ... Was soll den an der Aussage falsch sein? Ich kann auch sagen, wenn vor mir nen quadratischer Körper ist, das er rechteckig ist.



  • (D)Evil schrieb:

    "Ein Quadrat ist ein Rechteck" wahr und falsch sein kann

    Na von wegen ... Was soll den an der Aussage falsch sein? Ich kann auch sagen, wenn vor mir nen quadratischer Körper ist, das er rechteckig ist.

    Du liest am besten mal den Thread von vorne.



  • pumuckl schrieb:

    Das Problem ist, dass unsere gemeine Sprache nicht genau genug ist, so dass die Behauptung "Ein Quadrat ist ein Rechteck" wahr und falsch sein kann, je nachdem was wir unter Quadraten und Rechtecken verstehen.

    sag nicht 'ein quadrat ist ein rechteck' sondern sag' 'ein quadrat ist die erweiterung eines rechtecks' und dann merkst du schnell, dass der satz falsch ist (auch umgekehrt), denn mit vererbung kann man zwar etwas dazupacken, aber nichts wegnehmen.



  • @Bashar: Hmm ... tjo hab das schon zum groß Teil gelesen. Finde aber trotzdem keinen haltbaren Argumentationspunkt, ein Quadrat als gesonderten Fall anzusehen. Es sei denn du brauchst die besonderen Definitionen die nur bei einem Quadrat zutreffen. Quadrat ist nunmal laut Definition ein Rechteck**.** Von daher ist eine Basisklasse Rechteck, von der man keine Klasse Quadrat ableiten kann, falsch implementiert. Von Quadrat auf Rechteck ist es nat. anders ...



  • Und was genau ist dein Gegenargument? Es ging um die Methoden setWidth/setHeight eines Rechtecks, die man nicht korrekt in einem Quadrat implementieren kann. Ein Objekt ist nunmal in der OOP nicht nur ein bestimmtes Ding, sondern auch das, was man damit machen kann.
    Man könnte höchstens argumentieren (vielleicht tust du das ja, ohne es zu sagen), dass Rechteck dann eben diese Methoden nicht haben darf. Dann ist natürlich trivial jedes Quadrat auch ein Rechteck, die Frage, woran man solche Problem im Allgemeinen erkennt, bleibt aber bestehen.



  • Ich sehe nicht wo dein Problem ist? Die Funktion setWidth und setHeight würden halt im Quadrat überschrieben ... z.B. gibt es auch eine Basisklasse Tier. Wenn die nun die Funktion laufen hätten, würdest du bei manchen(den meisten) wohl auch die lauf-Funktion überschreiben müssen ... setWidth und setHeight würden im Quadrat das selbe machen, und zwar m_nHeight und m_nWidth auf den übergeben Wert setzen ... wo ist da euer Problem?


Anmelden zum Antworten