Das Liskovsche Substitutionsprinzip in JAVA ( Invarianz, Kovarianz, Kontravarianz )

Sicherlich fragt ihr euch nun “Was soll das denn?”.

Nun ja, ich fand es sehr schwer im Netz Informationen zum Thema “Substitutionsprinzip” zu finden, als ich die Übungsaufgaben der Veranstaltung “Informatik B – Objekorientiertes Programmieren in Java” von der Uni Osnabrück bearbeiten wollte, daher hier die schlussendliche Weisheit, die man zu Thema parat haben sollte, auf dass die nächste Generation von InfoBlern beim Googeln was brauchbares finden möge.

Was ist das eigentlich ?

Es geht dabei um Vererbung in der Objekt-Orientierung. Das Liskovsche Substitutionsprinzip ( Ersetzungsprinzip ) sagt aus, dass eine Unterklasse stets alle Eigenschaften der Oberklasse erfüllen und immer als Objekt der Oberklasse verwendbar sein muss. Eine Unterklasse darf Erweiterungen enthalten, nicht aber grundlegende Änderungen.

Was ist das da mit diesen Varianzen ?

Es geht hauptsächlich um die überschriebenen Methoden in Unterklassen, und wie sich dort die Argumenttypen, Rückgabetypen, sonstige Signaturerweiterungen ( z.B. Exceptions ) oder generische Klassenparameter gegenüber der Oberklasse verhalten.

Allgemein wird hier zwischen drei verschiedenen Varianzen unterschieden.

Bei den Beispielen wird davon ausgegangen, dass Student von Person erbt und Buch von Dokument.

Invarianz

Wie der Name schon sagt, es gibt keine Varianz. Die Typen sind sowohl in der Methode der Oberklasse, als auch in der überschriebenen Methode der Unterklasse gleich.

Person schreibt():Buch
Student schreibt():Buch

Kovarianz

Die Typhierarchie geht mit der Vererbungshierarchie.

Person schreibt():Dokument
Student schreibt():Buch

Kontravarianz

Die Typhierarchie ist entgegengesetzt zur Vererbungshierarchie.

Person liest(Buch)
Student liest(Dokument)

Wann erfüllen welche Varianzen nun wo das Liskovsche Substitutionsprinzip?

Eingabetypen

Auf der Eingabeseite ist das Prinzip erfüllt durch Invarianz und Kontravarianz.

Die Invarianz ist offensichtlich, wenn sowohl der Student, als auch die Person ein Buch lesen können, dann kann es kein Problem geben. Die Kontravarianz ist da dann wohl eher nicht so offensichtlich.

Wird eine Instanz einer Unterklasse einer Referenz vom Typ der Oberklasse zugewiesen, so wird die Oberklasse durch die Unterklasse substituiert. Eine typsichere Substitution erfordert, dass alles was vorher mit der Oberklasse möglich war, ebenfalls mit der Unterklasse machbar ist.

Person p = new Student();

Das bedeutet, dass die Unterklasse mindestens fähig sein muss, alle Eingaben, die bei der Oberklasse gemacht werden können, entgegen zu nehmen. Das ist ( außer natürlich bei Invarianz ) nur bei Kontravarianz der Fall. Ruft man die Methode liest() nun mit einem Buch auf ( p.liest(Buch) ), so kann der Student damit umgehen, da er bei Einhaltung der Kontravarianz mit sämtlichen Dokumenten klarkommt. Umgekehrt wäre das nicht der Fall.

Rückgabetyp

Im Rückgabetyp wird das Liskovsche Substitutionsprinzip bei Invarianz und Kovarianz erfüllt. Der Fall der Invarianz ist wie bei der Eingabe trivial. Wenn sowohl Student als auch Person ein Buch schreiben können, so ist das Prinzip natürlich erfüllt. Auch der Fall der Kovarianz liegt im Grunde auf der Hand. Gibt ein Person allgemein ein Dokument zurück und der Student ein Buch, so entsteht auch hier kein Problem, da man auf jedes beliebige Dokument in der Rückgabe vorbereitet ist, z.B. auch auf ein Buch.

Wie funktioniert das nun in Java ?

Eingabeseite

Auf der Eingabeseite unterstützt Java direkt nur die Invarianz. Kontravarianz würde zwar auch das Liskovsche Substitutionsprinzip erfüllen, aber die Methode hätte dann eine andere Signatur, und wäre somit nicht überschrieben, sondern überladen, und somit vom Compiler bereits anhand des Referenztypen, an dem sie aufgerufen wird, entsprechend zugeordnet.

Rückgabeseite

Auf der Rückgabeseite unterstützt Java sowohl die Invarianz als auch die Kovarianz. Sind die Rückgabetypen einer überschriebenen Mehode kovariant, so ist diese tatsächlich überschrieben. Normalerweise würde ein abweichender Rückgabetyp bei einer überschriebenen Methode zu einem Compiler-Fehler führen. Kovariante Rückgabetypen in Unterklassen bilden hier eine Ausnahme. Wenn also eine Frage nach einer Ausnahme in dem Zusammenhang auftauchen sollte, dann meinen die genau das damit. ;-)

Ich hoffe, damit sind nun alle Klarheiten beseitigt. Mich hat das Thema besonders verwirrt, aber durch das Niederschreiben ist mir das nun auch selbst endgültig bewusst geworden. Ich hoffe, alle anderen Gepeinigten können mit den Ausführungen mehr anfangen, als mit den paar fadenscheinigen Stichworten auf der Vorlesungsfolie.

  • Lieber Anonym

    Voll gut!

  • http://www.dsa-vision.de/wordpress Alex

    Das Java wieder aus der Reihe tanzt, war ja klar,
    nur uns erst nicht :p

    Aber der Artikel trifft den Kern des Prinzips sehr gut.

    Weiter so ;)

  • Manni

    Einfach, schnell und sehr gut erklärt!
    Thx! =)

  • Basti

    Gute Arbeit!…beste Erklärung die man finden kann!

  • Chris

    Danke Nils man sieht sich morgen ;)

  • http://www.codedomain.de/peanuts/codedom.nsf/d6plinks/JREK-83JRXU JakeJlues

    Verständlich!

  • http://www.codedomain.de/peanuts/codedom.nsf/d6plinks/JREK-83JRXU JakeJBlues

    Cool und verständlich

  • says

    sehr cool, vielen dank von aktuellem infob-ler..

  • Nils

    Freut mich, dass es tatsächlich jemand was gebracht hat. :-)

  • PeterP

    ein hoch auf Elke P. ;)

  • Bilal

    Danke! Eine sehr verständliche Erklärung

    • Nils

      Vielen Dank, sehr hilfreich. Ist anscheinend halb so wild, wie gedacht.

      ~aktuell aus InfoB ;)

      • Anonymous

        Freut mich, dass es dir geholfen hat. Immer wieder lustig zu sehen wie die Klickrate für diesen Artikel Anfang Mai hochgeht. :P

  • Nils

    Vielen Dank, sehr hilfreich. Ist anscheinend halb so wild, wie gedacht.

    ~aktuell aus InfoB ;)

    • http://www.nils-haldenwang.de NilsHaldenwang

      Freut mich, dass es dir geholfen hat. Immer wieder lustig zu sehen wie die Klickrate für diesen Artikel Anfang Mai hochgeht. :P

  • Frithjof

    Vielen Dank, das ist mal verständlich erklärt!
    Das Vorlesungsskript hat mich mehr verwirrt als sonstwas :D

    • Jan

      Die Beschreibung hat mir schon weitergeholfen. Allerdings frage ich mich, wieso man folgendes codiert:

      Person p = new Student();

      Theoretisch betrachtet völlig korrekt, aber wann ich brauche ich es denn zwingend in der Praxis. Ich kann doch gleich schreiben: Student s = new Student();

      Habe ich da was  nicht korrekt verstanden.

      Grüße aus Bremen

      • Anonymous

        Hallo Jan,

        das ist dann sinnvoll, wenn man mehrere Klassen hat, die von Person erben, z.B. Professor, Student, Doktorand, Sekretärin oder was man sich sonst so vorstellen kann. Wenn es an dieser Stelle nur darauf ankommt die Fähigkeiten einer Person einzusetzen, dann kann ich jedes beliebige Objekt verwenden, dass von Person erbt. Das ist insbesondere dann sinnvoll, wenn das Objekt von außen kommt und ich es nicht direkt erzeuge. Es spielt für mich dann keine Rolle, ob es ein Student oder ein Professor ist, da ich angekündigt habe nur Eigenschaften von Person zu verwenden.

        In der Tat kommt der praktische Nutzen bei den Beispielen nicht unbedingt so durch. Das ist immer der Tradeoff, mit dem man zu kämpfen hat. Sind die Beispiele zu einfach, dann versteht man es schlecht, wenn sie aber so trivial sind, dass man das Prinzip begreift, dann geht meistens der Nutzen verloren.

        Viele Grüße

        Nils

        • Jan

          Hallo Nils,

          vielen Dank für Deine Antwort. Ja, das kann ich jetzt nachvollziehen :-)

          Viele Grüße

          Jan

    • Bernhard Streit

      Das kommt in der Praxis eher nicht direkt vor (also mit new-Operator), aber wenn Du z.B. eine Factory hast, die ein Objekt vom Typ Person erzeugt und zurückgibt, dann musst Du im Code natürlich die Variable vom Typ “Person” deklarieren.

      Ersetzt Du die Factory durch eine Factory, die Studenten zurückgibt, hast Du praktisch die von Dir beschriebene Situation – ein Student wird erzeugt, aber der Code, der mit dem Studenten arbeitet, interessiert sich nur für die Eigenschaften der Klasse Person.

      Das hat Vorteile – der Code kann dann mit sämtlichen Factories arbeiten, egal ob sie Studenten, Professoren oder was auch immer zurückgeben, ohne dass man dort irgendetwas ändern muss. Siehe Open-Closed-Principle :)

  • Frithjof

    Vielen Dank, das ist mal verständlich erklärt!
    Das Vorlesungsskript hat mich mehr verwirrt als sonstwas :D

  • Jan

    Die Beschreibung hat mir schon weitergeholfen. Allerdings frage ich mich, wieso man folgendes codiert:

    Person p = new Student();

    Theoretisch betrachtet völlig korrekt, aber wann ich brauche ich es denn zwingend in der Praxis. Ich kann doch gleich schreiben: Student s = new Student();

    Habe ich da was  nicht korrekt verstanden.

    Grüße aus Bremen

    • http://www.nils-haldenwang.de NilsHaldenwang

      Hallo Jan,

      das ist dann sinnvoll, wenn man mehrere Klassen hat, die von Person erben, z.B. Professor, Student, Doktorand, Sekretärin oder was man sich sonst so vorstellen kann. Wenn es an dieser Stelle nur darauf ankommt die Fähigkeiten einer Person einzusetzen, dann kann ich jedes beliebige Objekt verwenden, dass von Person erbt. Das ist insbesondere dann sinnvoll, wenn das Objekt von außen kommt und ich es nicht direkt erzeuge. Es spielt für mich dann keine Rolle, ob es ein Student oder ein Professor ist, da ich angekündigt habe nur Eigenschaften von Person zu verwenden.

      In der Tat kommt der praktische Nutzen bei den Beispielen nicht unbedingt so durch. Das ist immer der Tradeoff, mit dem man zu kämpfen hat. Sind die Beispiele zu einfach, dann versteht man es schlecht, wenn sie aber so trivial sind, dass man das Prinzip begreift, dann geht meistens der Nutzen verloren.

      Viele Grüße

      Nils

      • Jan

        Hallo Nils,

        vielen Dank für Deine Antwort. Ja, das kann ich jetzt nachvollziehen :-)

        Viele Grüße

        Jan

  • Jon

    super erklärt!!! Danke dir!

  • Jon

    super erklärt!!! Danke dir!

  • DankeSagen

    Danke dir, hast mein Testat morgen gerettet :) btw: deine Seite ist die 2. die google mir ausgespuckt hat.