Home > Informatik > Stufe Q1

Vererbung

Allgemeines - Klassendiagramme - Mehrfachvererbung

Problemstellung

In dem Dungeon-Spiel, das ich häufig mit meinen Schülern der Stufen EF oder Q1 programmiere, kommen regelmäßig zwei wichtige Java-Klassen vor: Held und Monster. Im Grunde unterscheiden sich die beiden Klassen nur wenig. Beide Klassen verfügen über Attribute wie angriff, verteidigung, leben und so weiter. In den fortgeschrittenen Versionen des Spiels können sich die Objekte beider Klassen auch bewegen, Gegenstände aufnehmen, kämpfen und so weiter.

Vergleich zweier ähnlicher Klassen

Kurzfassung einer Klasse Held:

public class Held
{
    int x,y;
    double stufe, leben, angriff, verteidigung;
    Gegenstand[] dinge;
    int anzahlDinge;
    
    public Held(int xPos, int yPos)
    {
        x = xPos;
        y = yPos;
        stufe = 1;
        leben = 100;
        angriff = 20;
        verteidigung = 20;
        dinge = new Gegenstand[12];
        anzahlDinge = 0;
    }
    
    public void goWest()
    {
       if (x > 0) x--;
    }

    ...
    
    public int getLeben()
    {
       return (int) Math.round(leben);
    }
    
    public void aufnehmen(Gegenstand neu)
    {
       if (anzahlDinge < 12)
          dinge[anzahlDinge++] = neu;
    }
}

Kurzfassung einer Klasse Monster:

public class Monster
{
    int x,y;
    double stufe, leben, angriff, verteidigung;
    
    public Monster(int xPos, int yPos)
    {
        x = xPos;
        y = yPos;
        stufe = 1;
        leben = 100;
        angriff = 15;
        verteidigung = 15;
    }
    
    public void goWest()
    {
       if (x > 0) x--;
    }

    ...
    
    public int getLeben()
    {
       return (int) Math.round(leben);
    }
}

Wie man leicht sieht, unterscheiden sich diese beiden Klassen kaum. Man könnte gut damit arbeiten. Jetzt macht der Projektleiter aber eine neue Vorgabe, beispielsweise dass sowohl Helden wie auch Monster im Spiel verletzt werden können und dabei Lebenspunkte einbüßen. In diesem Fall müssen alle Klassen, die Lebewesen repräsentieren, also auch Held und Monster, überarbeitet werden. Wenn mehrere solche Klassen auf die gleiche Weise verändert werden müssen, macht man oft Fehler.

Eine "beliebte" Fehlerquelle ist das gedankenlose Verwenden von Copy & Paste. Man verändert den Quelltext der Klasse Held und kopiert die Veränderung dann einfach in den Quelltext der Klasse Monster. Dabei übersieht man aber oft bestimmte Kleinigkeiten, die dann zu Fehlern führen.

Der Mechanismus der Vererbung, der nahezu allen objektorientierten Programmiersprachen zur Verfügung steht, verhindert nicht nur solche Fehler, sondern erspart viel Tipparbeit. Auch dadurch werden Fehler verhindert, die durch Stress und Ermüdung entstehen.

Vererbung

Hunde und Katzen gehören beide zur Klasse der Raubtiere. Alle Raubtiere haben gemeinsame Eigenschaften und Verhaltensweisen, zum Beispiel ein typisches Raubtiergebiss, scharfe Krallen, einen kurzen Fleischfresser-Darm, hoch entwickelte Sinnesorgane, einen Jagdinstinkt und so weiter.

Auch bei der Modellierung von realen Objekten treten solche Gemeinsamkeiten auf. Objekte der Klasse Held und Objekte der Klasse Monster haben gemeinsame Eigenschaften und Verhaltensweisen.

Gemeinsame Eigenschaften:

Die Attribute x, y, stufe, leben, angriff und verteidigung.

Gemeinsame Verhaltensweisen:

Die Methoden goWest, goNorth etc., getLeben und andere.

Genauso wie man sagen kann, dass Hunde und Katzen zu den Raubtieren gehören, kann man auch sagen, dass Helden und Monster zu den Lebewesen gehören.

Wir erzeugen also eine völlig neue Klasse Lebewesen,  die alle Attribute und Methoden besitzt, die auch Helden und Monster haben. Dann leiten wir die Klassen Held und Monster von dieser Klasse Lebewesen ab. Das Grundprinzip heißt hier Vererbung.

Die Klasse Lebewesen:
public class Lebewesen
{
    int x,y;
    double stufe, leben, angriff, verteidigung;
    
    public Lebewesen(int xPos, int yPos)
    {
        x = xPos;
        y = yPos;
        stufe = 1;
        leben = 100;
        angriff = 10;
        verteidigung = 10;
    }
    
    public void goWest()
    {
       if (x > 0) x--;
    }
    
    public void goNorth()
    {
       if (y > 0) y--;
    }

    ...
    
    public int getLeben()
    {
       return (int) Math.round(leben);
    }
    
    public void levelUp()
    {
       if (stufe < 80)
       {
           stufe++;
           angriff *= 1.1;
           verteidigung *= 1.1;
           leben = 100;
       }
    }
    
}

Natürlich ist dieser Quelltext nicht vollständig; viele Methoden wie zum Beispiel goEast oder goSouth fehlen noch. Es geht hier ja nur ums Prinzip.

Betrachten wir nun die Klassen Held und Monster, die sich von Lebewesen ableiten. Zunächst die Klasse Held:

public class Held extends Lebewesen
{
    Gegenstand[] dinge;
    int anzahlDinge;
    
    public Held(int xPos, int yPos)
    {
        super(xPos,yPos);
        angriff = 20;
        verteidigung = 20;
        dinge = new Gegenstand[12];
        anzahlDinge = 0;
    }
    
    public void aufnehmen(Gegenstand neu)
    {
       if (anzahlDinge < 12)
          dinge[anzahlDinge++] = neu;
    }
}

Und dann die Klasse Monster:

public class Monster extends Lebewesen
{
    public Monster(int xPos, int yPos)
    {
        super(xPos,yPos);
        angriff = 25;
        verteidigung = 25;
    }
}

"Vererbung" heißt, dass die Klasse Held alle Attribute und Methoden übernimmt, die in der "Mutterklasse" Lebewesen definiert worden sind. Alle Objekte der Klasse Held besitzen also ein Attribut leben, ein Attribut angriff und so weiter, und alle Objekte der Klasse Held verfügen über eine Methode goWest, goNorth, getLeben und so weiter. Das Gleiche gilt für alle Objekte der Klasse Monster, auch sie besitzen alle Attribute und Methoden, die in der Klasse Lebewesen definiert worden sind.

Die Tochterklassen (abgeleitete Klassen, Unterklassen, Subklassen, Kindklassen) können einen eigenen Konstruktor besitzen, so wie in den obigen Beispielen. Mit der Anweisung super() kann dann der Konstruktor der Mutterklasse (Basisklasse, Superklasse, Oberklasse, Elternklasse) aufgerufen werden; die benötigten Parameter müssen dann mit übergeben werden.

Für Experten

Ganz korrekt ist die Aussage nicht, dass die Tochterklassen alle Attribute und Methoden der Mutterklasse erben. Attribute und Methoden, die in der Mutterklasse als private deklariert worden sind, können nicht vererbt werden.

Betrachten wir mal den Anfang der zu Testzwecken leicht ergänzten Klasse Lebewesen:

public class Lebewesen
{
    int x,y;
    double stufe, leben, angriff, verteidigung;
    
    private int test;
    ...

Wir wollen nun im Konstruktor der Klasse Held dieses private-Attribut ändern:

    public Held(int xPos, int yPos)
    {
        super(xPos,yPos);
        angriff = 20;
        verteidigung = 20;
        dinge = new Gegenstand[12];
        anzahlDinge = 0;
        test = 213;
    }

Beim Kompilieren der Klasse Held zeigt uns BlueJ dann eine Fehlermeldung: "test has private access in Lebewesen".

Wie die obigen Beispiele zeigen, kann eine Tochterklasse neue Attribute und Methoden besitzen. Die Klasse Held verfügt zum Beispiel über einen Objektarray dinge der Klasse Gegenstand, damit der Held Dinge aufsammeln kann, die im Dungeon herumliegen. Die Monster können das nicht, daher brauchen sie auch keinen solchen Objektarray. Die Methode aufnehmen() kommt auch nur in der Klasse Held vor, da die Monster keine Gegenstände aufnehmen können.

In der realen Welt gibt es ähnliche Phänomene. Alle Raubtiere haben beispielsweise Krallen, aber die katzenartigen Raubtiere haben zusätzlich die Möglichkeit, die Krallen einzuziehen, während hundeartige Raubtiere das nicht können.

Damit haben wir schon einmal ein paar grundlegende Dinge über Vererbung gelernt.

Vererbung ist ein wichtiges Grundprinzip der objektorientierten Programmierung. Bei der Vererbung übernehmen die Tochterklassen alle nicht als private deklarierten Attribute und Methoden der Mutterklasse. De facto IST eine Tochterklasse identisch mit der Mutterklasse, daher bezeichnet man die Vererbungsbeziehung auch als IST-Beziehung.

Die Bezeichnung IST-Beziehung ist nicht perfekt, denn wie wir gerade gesehen haben, können die Tochterklassen zusätzliche Attribute und Methoden besitzen. Umgekehrt werden private Attribute und Methoden der Mutterklasse nicht übernommen.

Seitenanfang -
weiter mit Klassendiagrammen...