Vererbung und Polymorphie

Status des Themas:
Es sind keine weiteren Antworten möglich.

Diskutiere Vererbung und Polymorphie im Java-FAQ Beiträge Forum; Dies ist ein kleiner Crash-Kurs in Sachen Vererbung und Polymorphie im Allgemeinen. Polymorphie ist ein wesentlicher, wenn nicht der wesentlichste...

  1. hdi
    hdi Neues Mitglied
    Dies ist ein kleiner Crash-Kurs in Sachen Vererbung und Polymorphie im Allgemeinen. Polymorphie ist ein
    wesentlicher, wenn nicht der wesentlichste Bestandteil von OO-Programmierung. Los geht's:

    Vererbung
    Vererbung bedeutet: Wenn man mehrere Objekte hat, die in Teilen identisch sind, d.h. in ihrem Aussehen
    oder ihren Eigenschaften (Attribute) oder ihrem Verhalten (Methoden) teils gleich sind, sollte man
    Vererbung verwenden. Dazu findet man eine sog. "Oberklasse / Superklasse" für all diese Objekte. Sie besteht
    dann genau aus diesen Dingen, in denen sich alle Objekte gleich sind.
    Wenn man nun mehrere solch ähnliche Objekte hat, kann man einfach von der Oberklasse erben, und spart
    sich dadurch erstens viel Schreibarbeit, und nutzt zum zweiten (und das ist das wichtige) die Polymorphie
    von Java.

    Bsp:

    Code (Text):
    public class Auto{

       
       private int ps;
       private String marke;
       private String modell;
       private Color lackFarbe;
       private double marktWert;
       private String baujahr;
       
       public Auto( String marke, String modell, String baujahr){
             this.marke = marke;
             this.modell = modell;
             this.baujahr = baujahr;
       }

       // ...

    }
    Wenn ich nun einen BMW und einen Audi haben will, so sind das beides Autos, und sie haben beide all die Attribute
    der Klasse Auto. Statt also zweimal die gleiche Klassenstruktur aufzuziehen, mache ich Auto zur Superklasse und
    erbe dann davon:

    Code (Text):
    public class BMW extends Auto{}
    public class Audi extends Auto
    Polymorphismus heisst nun:
    Java nimmt überall dort, wo eine Klasse erwartet wird, auch jede Unterklasse dieser Klasse an.

    So könnte ich zB die Methode

    Code (Text):
    kaufeAuto (Auto a){...}
    aufrufen mit:

    Code (Text):
    kaufeAuto (new BMW( "BMW", "7er", "2007"));
    oder mit:

    Code (Text):
    kaufeAuto (new Audi( "Audi", "A8", "2005"));
    Das ist also der Vorteil von Vererbung. Ziel beim Programmieren ist es, eine Klassen-Struktur zu finden,
    sodass an jeder Stelle im Programm jeweils nur die Informationen gekapselt sind, die mich interessieren.
    Damit meine ich: Wenn ich eine Methode habe

    Code (Text):
    verkaufeAuto (Auto a){...}
    Angenommen diese Methode steht in einer Klasse "Autohändler", dann ist es mir völlig Wurscht ob das
    nun ein BMW oder ein Audi oder sonst was ist. Ich bin ja kein BMW-Händler oder Audi-Händler. Ich bin
    Auto-Händler, und was ich will, das sind Autos. Und diese Abstrahierung von einem BMW zu einem
    "Auto" ganz generell ist der Polymorphismus: Die Klasse Autohändler kennt keine spezifischen Autos,
    sondern nur allgemein Autos. D.h. auch, dass ich jederzeit neue Auto-Unterklassen erstellen oder löschen
    kann, ohne dass ich im Code von "Autohändler" etwas ändern muss.

    Vererbung, Sichtbarkeit und Overriding
    Im Zusammenhang mit Vererbung sollte man auch das Kapitel der Sichtbarkeit nicht weglassen.
    Es gibt in Java 3 verschiedene Schlüsselwörter, mit denen man die Sichtbarkeit einer Variablen oder
    einer Methode spezifizieren kann. "Variablen oder Methode" bezeichne ich im Folgenden als v/m :

    1) private
    2) protected
    3) public

    private heisst: v/m ist nur in der Klasse, in der die v/m definiert ist, sichtbar.
    protected heisst: v/m ist in allen Klassen sichtbar, die sich im selben Package befinden wie die Klasse,
    in der die v/m definiert ist.
    public heisst: v/m ist überall sichtbar
    Anmerkung: Man kann ein Schlüsselwort auch komplett weglassen, ich bin mir nicht sicher aber ich
    glaube dann ist es immer automatisch protected, oder irgendein Zwischending zwischen protected
    und was anderem ;) Ist nicht so wichtig, weil man sowas eh nie machen sollte.

    Damit klar ist, was ich mit "sichtbar" meine: Sichtbar heisst einfach, ich kann auf die v/m zugreifen
    an einer gewissen Stelle/Klasse im Programm.

    Wie hängt das jetzt also mit Vererbung zusammen?
    public und protected v/m werden vererbt und sind in der Unterklasse sichtbar.
    private v/m werden implizit auch vererbt, sind aber in Unterklassen nicht sichtbar.

    Hier besteht also der kleine aber feine Unterschied bei private v/m: Sie sind weder in Unterklassen
    noch in komplett anderen Klassen sichtbar, aber wenn man sie vererbt, sind sie in der Unterklasse
    "da", wenn auch versteckt. Ich denke ohne Beispiel wird das nicht ganz klar:

    Code (Text):
    public class Auto{

         private int ps;

         public Auto (int ps){
              this.ps = ps;
         }
         public int getPs(){
               return this.ps;
         }
    }
     
    Code (Text):
    public class BMW extends Auto{

         public void pimp(){
               this.ps += 20;  // Compile-Error: Variable "ps" nicht bekannt
         }
    }
    Aber:

    Code (Text):
    BMW myBmw = new BMW(200);
    myBmw.getPs(); // <- liefert "200", also gibt es "ps" scheinbar doch ?!
    Merke: Auch wenn private v/m in Unterklassen nicht direkt sichtbar sind, so sind sie doch da, weil es ja
    eine Unterklasse ist, und sie demnach alle Attribute und Methoden der Superklasse hat. Zugreifen tut
    man auf private Attribute immer mit - öffentlichen (public) - "Gettern" und "Settern".

    Jetzt noch zu dem Wort "Override":
    Das heisst ßberschreiben von Methoden aus der Superklasse in Unterklassen. Wenn wir uns das obige Bsp
    mit Auto und BMW ankucken, wäre das hier ein Override:

    Code (Text):
    public class BMW extends Auto{

         @Override
          public int getPs(){
                   // ein Aufruf von bmw.getPs() liefert jetzt nicht mehr "ps" der Oberklasse, sondern das,
                   // was hier drin steht!! -> Methode wurde überschrieben!
                   return 10;
          }
    }
    Kommentar sagt alles. Overriding ist eher nicht so toll, zumindest nicht von eigenen Methoden.
    Dennoch ist die ursprüngliche Methode getPs() der Superklasse "Auto" nicht verloren. Man greift
    auf Attribute und Methoden der Oberklasse per "super" zu:

    Code (Text):
    @Override
          public int getPs(){
                  return super.getPs(); // ßberschrieben aber wieder zur Oberklassen-Methode gelenkt.
                                                    // Macht eindeutig eher nicht so viel Sinn...
          }

    Mehrfach-Vererbung:
    Es sei noch erwähnt, dass "echte" Mehrfach-Vererbung in Java nicht möglich ist:

    Code (Text):
    public class BMW extends Auto extends Fahrzeug{} // Compile-Fehler!
    Allerdings kann man über Verschachtelug von Super-/und Unterklassen eine Art Mehrfach-Vererbung
    erreichen. so geht folgendes ohne Probleme:

    Code (Text):
    public class Fahrzeug{}
    public class Auto extends Fahrzeug{}
    public class BMW extends Auto{}
    Ein Objekt vom Typ BMW wäre nun sowohl ein BMW, als auch ein Auto, als auch ein Fahrzeug. Wenn
    mein Autohändler also noch allgemeiner wäre, und nicht nur Autos annehmen würde sondern auch
    Motorräder, könnte ich in einer Methode

    Code (Text):
    verkaufe ( Fahrzeug f){}
     
    nach wie vor einen BWM übergeben. Weil BMW = Auto = Fahrzeug! Das lässt sich beliebig weiter verschachteln.

    das Schlüsselwort 'abstract'
    Im Zusammenhang mit Vererbung wird auch oft das Keyword 'abstract' benutzt. Die Bedeutung ist im
    Prinzip einfach der deutschen ßbersetzung zu entnehmen: "Abstrakt" ist ein Ding, dass es so in der
    Realität gar nicht gibt, sondern nur eine Art Muster oder Rohfassung etc. ist...

    Abstrakte Klassen:
    ... heisst jetzt also, ich habe etwas, das es so an sich gar nicht gibt, Bsp:

    Code (Text):
    public abstract class Auto{}
    Das trifft auch tatsächlich zu, denn gibt es sowas wie ein "Auto"? Nein! Du kannst nicht irgendwohin gehen
    und sagen "ich möchte ein Auto". Das gibt es gar nicht. Es gibt nur BMW, Audi, VW, ... kein "Auto".
    Deshalb ist die Klasse Auto ein gutes Beispiel für eine abstrakte Klasse. Was bringt das nun? Naja,
    wenn eine Klasse abstrakt ist, heisst das einfach nur: Man kann keine Instanz davon erstellen:

    Code (Text):
    new Auto(); // Compile-Error: Auto ist abstrakt, kann kein "Auto" erstellen
    Das ist eigentlich alles, bringt also programmiertechnisch quasi nur die Sicherheit, dass man gar nicht erst
    versuchen kann, ein Auto zu erstellen. Mehr nicht, man kann es genauso gut weglassen wenn man selbst
    darauf achtet, nur spezifische Unterklassen zu erstellen.

    Abstrakte Methoden:
    Abstrakte Methoden sind jetzt aber schon vielmehr auch praktisch nützlich und ein ganz wesentlicher
    Bestandteil von Polymorphie! ßber Vererbung habe ich gesagt, man nimmt sie, wenn Teile verschiedener Objekte
    gleich sind. Vererbung zusammen mit abstrakten Methoden nimmt man dahingegen dann, wenn die
    Funktionalität von den Objekten gleich sein soll (was sie können), aber die Implementierung unterschiedlich
    ist auf Grund der Unterschiede zwischen den Klassen. (und nicht gleich wie bei Vererbung).

    Bsp: Ein "Kreis" und ein "Quadrat" sind beides geometrische Figuren. Sie haben beide zB eine Position im
    Koordinaten System (x,y), haben beide eine gewisse Grösse (Durchmesser bzw. Kantenlänge), und können zB
    auch beide in einem JPanel gemalt werden.

    Man kann das aber nicht mit "reiner" Vererbung lösen:

    Code (Text):
    public abstract class GeometrischeFigur{

           public void male(Graphics g){
                // tja, wie malt sich eine "geometrische Figur". Was sind die Gemeinsamkeiten im Zeichenvorgang
                // von Kreisen und Quadraten?? Antwort: KEINE!
           }
    }

    public class Kreis extends GeometrischeFigur
    public class Quadrat extends GeometrischeFigur
    Das Problem steht im Kommentar: Man die Methode male() nicht so schreiben, dass sie für alle geometrischen
    Figuren funktioniert. Die einzige Lösung wäre über irgendwelche Abfragen:

    Code (Text):
    public void male(Graphics g){
                if ( this instanceof Kreis ){
                       g.drawOval(...)
                }
                else if (this instanceof Quadrat){
                       g.drawRect(...)
                }
    }
    Sieht gar nicht so schlimm aus? Jetzt stell dir vor, es gibt nicht 2 verschiedene geometrische Objekte, sondern 200.
    Viel Spass beim if-else-Hirntod! Und noch schlimmer: Wenn es nicht nur die Methode male() für jedes geometrische
    Objekt geben soll, sondern auch noch die Methoden "verschiebe()", "drehe()", "färbe()", ...
    Wie lange hockt man dann davor, um eine einzige neue Funtkionalität zu implementieren?

    Die Lösung für das Problem sind abstrakte Methoden. Eine abstrakte Methode wird in der Superklasse nur definiert,
    nicht deklariert,also nicht implementiert:

    Code (Text):
    public abstract class GeometrischeFigur{

          public abstract void male(Graphics g); // kein Rumpf!
    }
    Jede Unterklasse, die jetzt von GF erbt, muss diese Methode für sich selbst implementieren, je nach dem wie
    es halt passt. Die Klassen "Kreis" und "Quadrat" würden jetzt so aussehen:

    Code (Text):
    public class Kreis extends GeometrischeFigur{
         @Override
          public void male(Graphics g){
                 g.drawOval(...); // eigene Implementierung: zeichen Kreis
          }
    }
    Code (Text):
    public class Quadrat extends GeometrischeFigur{
         @Override
          public void male(Graphics g){
                 g.drawRect(...); // eigene Implementierung: zeichen Rechteck
          }
    }
    So, jetzt haben wir noch immer diese schöne Kapselung wie auch oben bei der Vererbung ohne abstrakte
    Methoden, d.h. ein "Geometrie-Scanner" würde alle Typen von Superklasse GemoetrischeFigur erkennen,
    und das gute ist, er kannauch alle korrekt malen:

    Code (Text):
    /* Dummer Klassen-Name: Verwaltet einfach geometrische Figuren und macht etwas damit */
    public class Scanner{

            private List<GeometrischeFigur> meineFiguren;

            public void maleAlles(Graphics g){
                       for( GeometrischeFigur gf : meineFiguren ){
                            gf.male(g);
                       }
            }
    }
    Das tolle: Der Scanner hat keinen Plan, was "gf" jetzt im einzelnen ist, ob ein Kreis oder ein Quadrat oder sont
    was. Ihm ist es aber egal: Er weiss, er hat geometrische Objekte, und diese Klasse bietet die Methode "male()",
    also kann er sie aufrufen. Was genau da passiert ist ihm Wurscht. Und das übernehmen halt die einzelnen
    Unterklassen davon jeweils für sich.

    Interfaces
    Es gibt kaum einen Unterschied zwischen Interfaces und abstrakten Methoden:

    Code (Text):
    public abstract class Paintable{
           public abstract void paint();
           //Bem.: Sobald eine Klasse eine abstrakte Methode hat,
           //MUSS die Klasse selbst auch abstrakt sein.
    }
     
    und
    Code (Text):
    public interface Paintable{
           public void paint();
    }
    sind so ziemlich das gleiche. Ein Grund für Interfaces ist das o.g. Problem der Mehrfachvererbung.
    Man könnte also die zweite Klasse, von der man erben will, als Interface erstellen und dieses dann
    implementieren. Beachte, dass man bei Interfaces so viele implementieren kann, wie man will:

    Code (Text):
    public class MegaClass extends MegaPower implements Interface1, Interface2, Interface3, .... // <-Okay!
    Interfaces funktionieren also im Prinzip genauso wie das Erben von Superklassen mit abstrakten Methoden,
    nur hat das Erben immer eine Einschränkung auf Sicht der Polymorphie. Deshalb gilt i.d.R:
    Code (Text):
    Wenn man die Wahl hat zwischen der Implementierung eines Interfaces oder der Erbung von einer Klasse mit
    abstrakten Methoden, sollte man immer lieber zum Interface greifen!
    Es ist einfach flexiblerer Code.

    Zusammenfassung / praktische Tipps
    Manch einer denkt sich jetzt vllt: Das ist ja alles schön und gut, aber nur weil man jemandem die Regeln
    von Schach erklärt hat, heisst das noch lange nicht, dass er ein Spiel auch nur annähernd gewinnen kann.
    Ich hab diese Verbindung zu Schach mal irgendwo gelesen und fand sie extrem passend! Programmieren ist wie
    Schach: Man lernt es, indem man es tut!
    Trotzdem gibt es wie auch im Schach ein paar Tipps, ein paar Regeln, die einem einen Stubs in die
    richtige Richtung geben können. Wenn man weiss, auf was man achten muss, erkennt man auch Gefahren.

    Die folgenden Dinge kann man sich durchaus auf ein Blatt Papier schreiben und immer mal wieder durchlesen,
    bis man sie verinnerlicht hat. Sie helfen beim Schreiben von gutem Design/Code:

    1) Switch-Konstrukte oder nicht-elementare if-Statements (alles, was nicht mit <,>=,==,|| etc geprüft wird)
    weisen auf einen Design-Fehler hin, den man durch Polymorphismus lösen kann. Bsp: Methode male() vor
    und nach Benutzung von abstrakten Methoden
    2) Vererbte Dinge sind immer in den Unterklassen da, aber nur public v/m sind echt "sichtbar"
    3) Man verwendet immer private für Attribute einer Klasse und macht sich Getter/Setter um in Unterklassen
    (und evtl auch aus anderen Klassen) darauf zugreifen zu können
    4) Vererben tut man, wenn verschiedene Objekte in Teilen gleich sind und sich gleich Verhalten
    5) Abstrakte Klassen nutzt man, wenn es die Oberklasse an sich gar nicht gibt
    6) Abstrakte Methoden nutzt man dann, wenn man vererben möchte, die Funktionalitäten aber in jeder Unterklasse
    anders implementiert werden müssen, und nicht gleich sind.
    7) die Implementierung eines Interfaces ist sowas wie das Implementieren von abstrakten Methoden einer
    Superklasse, nur besser!


    Hoffe, ich konnte ein paar Leuten etwas klarer machen.

    Wer Fehler findet oder Anregungen hat, was ich noch vergessen hab, bitte Bescheid geben!
     
Die Seite wird geladen...

Vererbung und Polymorphie - Ähnliche Themen

Polymorphie, Vererbung, statischer Typ, Laufzeittyp
Polymorphie, Vererbung, statischer Typ, Laufzeittyp im Forum Java Basics - Anfänger-Themen
Vererbung / Polymorphie
Vererbung / Polymorphie im Forum Java Basics - Anfänger-Themen
Verständnisproblem Vererbung/Polymorphie
Verständnisproblem Vererbung/Polymorphie im Forum Java Basics - Anfänger-Themen
Polymorphie/Vererbung Verständnisproblem
Polymorphie/Vererbung Verständnisproblem im Forum Java Basics - Anfänger-Themen
Unterschied Vererbung und Polymorphie?
Unterschied Vererbung und Polymorphie? im Forum Allgemeine Java-Themen
Status des Themas:
Es sind keine weiteren Antworten möglich.
Thema: Vererbung und Polymorphie