Autor: HM_JDSZielgruppe: Sek I

Vererbung und abstrakte Klassen

Dieses Kapitel uebertraegt das sehr umfangreiche Arbeitsblatt zur Vererbung in eine strukturierte Buchfassung. Es verbindet drei Themen, die im Blatt mehrfach ineinandergreifen:

  1. Spezialisierung per Vererbung (extends)
  2. Ueberschreiben/Ueberladen und Sichtbarkeit
  3. abstrakte Klassen und abstrakte Methoden

Grundidee: "ist-ein" statt "hat-ein"

Abbildung 1

Das Arbeitsblatt startet mit dem Beispiel "Kreis ist eine spezielle Ellipse". Genau das ist eine klassische ist-ein-Beziehung (is-a).

  • Ellipse beschreibt Allgemeines.
  • Kreis spezialisiert dieses Allgemeine.

In Java:

class Kreis extends Ellipse {
    // zusaetzliche oder angepasste Logik
}

Vererbung bedeutet dabei nicht "Code kopieren", sondern strukturierte Wiederverwendung.

Was geerbt wird und was nicht

Wenn ClassB extends ClassA, dann uebernimmt ClassB die von ClassA geerbten Attribute und Methoden gemaess Sichtbarkeit.

Wichtig aus dem Arbeitsblatt:

  • Konstruktoren werden nicht vererbt.

Das ist logisch, weil Konstruktoren immer konkret zur jeweiligen Klasse gehoeren.

Konstruktoren in Vererbungshierarchien

Abbildung 1

In einer Subklasse kann ein Konstruktor der Oberklasse mit super(...) aufgerufen werden.

Regel:

  • super(...) muss als erste Anweisung im Konstruktor stehen.

Beispielidee aus dem Blatt (Kreis/Ellipse):

  • Ein Kreis hat einen Radius.
  • Eine Ellipse hat zwei Halbachsen.
  • Der Kreis-Konstruktor kann den Ellipse-Konstruktor aufrufen und zweimal denselben Radius uebergeben.

Das verhindert Duplikate und haelt Initialisierungslogik zentral.

Sichtbarkeit in der Vererbung

Abbildung 1

Abbildung 2

Das Arbeitsblatt fuehrt den Modifier protected als zentrale Stufe ein.

  • private: nur in der eigenen Klasse sichtbar
  • package-sichtbar (ohne Modifier): im selben Package sichtbar
  • protected: im Package plus in Subklassen sichtbar
  • public: ueberall sichtbar

Fuer Vererbungsdesign ist protected oft sinnvoll, wenn Subklassen bewusst auf internere Bausteine zugreifen sollen, aber nicht die ganze Welt.

Ueberschreiben (Override)

Hat eine Subklasse eine Methode mit gleicher Signatur wie in der Oberklasse, wird in Subklassenobjekten die neue Version verwendet.

@Override
public int methode(String s, int k) {
    int basis = super.methode(s, k);
    return basis + 1;
}

Wichtige Punkte:

  1. Dynamische Bindung: Das konkrete Objekt entscheidet zur Laufzeit.
  2. Die Oberklassenversion kann bei Bedarf mit super.methode(...) eingebunden werden.
  3. Sichtbarkeit darf beim Ueberschreiben nicht unzulaessig eingeschraenkt werden.

Ueberladen (Overload) bleibt etwas anderes

Abbildung 1

Abbildung 2

Ueberladen bedeutet: gleicher Name, unterschiedliche Parameterliste.

  • Beide Methoden koennen nebeneinander existieren.
  • Auswahl erfolgt anhand der uebergebenen Parameter.
  • Das hat nichts mit Vererbungs-Polymorphie durch Override zu tun.

Das Blatt trennt diese Begriffe bewusst, weil sie in der Praxis oft verwechselt werden.

Konstruktoren: this(...) vs super(...)

  • this(...): ruft einen anderen Konstruktor derselben Klasse auf.
  • super(...): ruft einen Konstruktor der Oberklasse auf.

Beide duerfen nur als erste Anweisung stehen.

Zusatzregel aus dem Blatt:

  • Wenn kein super(...) angegeben ist, versucht Java automatisch den parameterlosen Oberklassenkonstruktor aufzurufen.
  • Gibt es diesen nicht, entsteht ein Compilerfehler.

Abstrakte Klassen und abstrakte Methoden

Abbildung 1

Abbildung 2

Das Arbeitsblatt zeigt ein wichtiges Entwurfsmuster: In einer Oberklasse soll eine Methode vorhanden sein, aber es gibt dort noch keine sinnvolle allgemeine Implementierung.

Dann wird sie abstrakt definiert:

protected abstract void zeichnen();

Und die Klasse muss ebenfalls abstrakt sein:

public abstract class GeometrischeFigur {
    protected abstract void zeichnen();
}

Folgen:

  • Von abstrakten Klassen koennen keine Objekte direkt erzeugt werden.
  • Konkrete Subklassen muessen abstrakte Methoden implementieren (oder selbst abstrakt bleiben).

Warum abstrakt hier didaktisch so wichtig ist

Im Blatt wird die Figur-Hierarchie als Hauptbeispiel genutzt. Der zentrale Vorteil:

  • Gemeinsame Logik (z. B. Sichtbarkeit, Bewegung) liegt in GeometrischeFigur.
  • Spezifisches Zeichnen liegt in Ellipse, Rechteck, Dreieck.

So wird Duplikatcode vermieden, ohne falsche Scheingemeinsamkeiten zu erzwingen.

Entwurfsargument aus dem Arbeitsblatt

Der Text begruendet explizit:

  • zeichnen() gehoert fachlich zu allen Figuren,
  • kann aber nicht allgemein inhaltlich implementiert werden,
  • muss daher abstrakt in die Oberklasse,
  • damit Methoden der Oberklasse (z. B. setSichtbar) trotzdem auf zeichnen() zugreifen koennen.

Das ist ein sehr gutes Beispiel fuer saubere OOP-Modellierung.

Sichtbarkeit und abstrakt: kritische Kombination

Das Blatt enthaelt auch Warnungen:

  • Eine abstrakte Methode darf nicht so versteckt werden, dass sie in Subklassen nicht sinnvoll implementierbar ist.
  • private abstract waere im Vererbungsfluss widerspruechlich.

Darum ist protected abstract im Figurenkontext passend.

Praktische Refactoring-Hinweise (aus dem Blatt)

Abbildung 1

Abbildung 2

Das Arbeitsblatt fordert explizit, bestehende Klassen zu ueberarbeiten:

  • Methoden entlang der Hierarchie verschieben ("pull up"/"push down")
  • Namen konsistent anpassen
  • Design und Code synchron halten

Das ist wichtig, weil gute Vererbung selten im ersten Versuch perfekt ist.

Typische Fehler in Vererbungskapiteln

  1. Vererbung fuer Wiederverwendung missbrauchen Nicht jede Gemeinsamkeit ist eine is-a-Beziehung.
  2. private in Oberklasse blockiert Subklasse Dann kann noetige Erweiterung schwer oder gar nicht erfolgen.
  3. Override ohne @Override Tippfehler bleiben unbemerkt.
  4. Konstruktorregeln ignoriertsuper(...)/this(...) falsch positioniert.
  5. Abstraktion zu frueh oder zu spaet Zu frueh: unnoetig komplex, zu spaet: viel Duplikatcode.

Uebungen zur Vertiefung

  1. Entwirf eine Hierarchie GeometrischeFigur -> Rechteck/Kreis/Dreieck.
  2. Lege gemeinsame Bewegungslogik in die Oberklasse.
  3. Definiere protected abstract void zeichnen(); in der Oberklasse.
  4. Implementiere je Subklasse eine konkrete zeichnen-Variante.
  5. Fuege eine Methode setSichtbar(boolean) in die Oberklasse ein und pruefe, dass sie zeichnen() korrekt verwendet.
  6. Dokumentiere, welche Mitglieder private, protected oder public sein sollen und warum.

Zusammenfassung

Das Arbeitsblatt vermittelt nicht nur Syntax, sondern Designprinzipien:

  • Vererbung ist ein Modellierungswerkzeug fuer echte Spezialisierungen.
  • Ueberschreiben und Ueberladen muessen sauber unterschieden werden.
  • Abstrakte Klassen erlauben gemeinsame Struktur ohne falsche Scheingeneralisierung.

Wer diese Kapitelidee beherrscht, kann danach deutlich bessere Klassenhierarchien entwerfen und refaktorieren.

Vererbung nur bei echter Spezialisierung

Das Arbeitsblatt arbeitet mit Kreis/Ellipse und zeigt damit ein gutes Gegenbeispiel zu haeufigen Fehlentwuerfen: Vererbung ist kein allgemeiner Mechanismus fuer Codewiederverwendung, sondern fuer fachliche Spezialisierung.

Prueffrage vor jeder Vererbung:

  • Ist jedes Objekt der Subklasse wirklich ein Spezialfall der Oberklasse?

Wenn die Antwort unsicher ist, sind oft Komposition oder Schnittstellen die bessere Wahl.

Pull-up und Push-down bewusst einsetzen

Im Blatt wird Refactoring entlang der Hierarchie explizit gefordert. Zwei typische Operationen:

  1. Pull-up Gemeinsame Methode aus mehreren Subklassen in die Oberklasse verschieben.
  2. Push-down Spezifische Methode aus Oberklasse in passende Subklassen verlagern.

Diese Operationen sind kein Selbstzweck, sondern dienen der Frage: Wo liegt fachliche Verantwortung am passendsten?

@Override als Sicherheitsnetz

Beim Ueberschreiben sollte immer @Override gesetzt werden.

@Override
protected void zeichnen() {
    // konkrete Implementierung
}

Vorteil:

  • Tippfehler in Signaturen werden als Compilerfehler sichtbar.
  • Es ist sofort klar, welche Methoden polymorph wirken.

Dynamische Bindung konkret nachvollziehen

Das Arbeitsblatt beschreibt, dass beim Aufruf die Objektklasse entscheidend ist. Das laesst sich mit einem kleinen Test sichtbar machen:

GeometrischeFigur f = new Kreis(...);
f.zeichnen();

Obwohl Referenztyp GeometrischeFigur ist, laeuft die zeichnen-Version von Kreis. Genau das ist dynamische Bindung.

Sichtbarkeit beim Ueberschreiben

Eine ueberschriebene Methode darf nicht "enger" sichtbar werden als in der Oberklasse. Sonst waere die Ersetzbarkeit verletzt.

Beispielproblem:

  • Oberklasse: public void operation1()
  • Subklasse: private void operation1()

Das ist ungueltig, weil ein oeffentlich zugesagtes Verhalten nicht versteckt werden darf.

Abstrakte Klassen mit teilweiser Implementierung

Das Blatt zeigt abstrakte Klassen nicht als reine leere Schablonen, sondern als Mischform:

  • abstrakte Methoden fuer variierende Teile
  • konkrete Methoden fuer gemeinsame Logik

Gerade bei Figuren ist das stark:

  • gemeinsam: Position, Sichtbarkeit, Bewegung
  • variabel: konkrete Zeichenlogik

So entsteht maximale Wiederverwendung ohne inhaltliche Verfaelschung.

Template-Methoden-Idee im Figurenkontext

Eine saubere Erweiterung:

public abstract class GeometrischeFigur {
    private boolean sichtbar;

    public final void setSichtbar(boolean sichtbar) {
        this.sichtbar = sichtbar;
        if (sichtbar) {
            zeichnen();
        } else {
            loeschen();
        }
    }

    protected abstract void zeichnen();
    protected abstract void loeschen();
}

Hier ist der Ablauf fest, nur die Spezialschritte sind abstrakt. Das passt exakt zur Argumentation des Arbeitsblatts.

Konstruktorfluss in Hierarchien testen

Bei Vererbung sollten Konstruktoraufrufe sichtbar gemacht werden, z. B. mit Ausgaben. Damit erkennen Lernende:

  1. Oberklasse wird zuerst konstruiert.
  2. Dann folgt Subklasse.
  3. super(...) steuert Parameterweitergabe.

Dieses Verstaendnis ist spaeter fuer tiefe Hierarchien entscheidend.

Typische Designfehler bei Vererbung

  1. Zu frueh abstrakte Oberklasse bauen.
  2. Zu spaet gemeinsame Logik nach oben ziehen.
  3. Private Felder in Oberklasse, die Subklassen fachlich brauchen.
  4. Ueberladene Methoden mit Override verwechselt.
  5. Methoden in Oberklasse, die eigentlich nur fuer eine Subklasse gelten.

Das Arbeitsblatt liefert durch seine Refactoringaufgaben genau die passende Gegenuebung.

Uebungssequenz fuer Vertiefung

  1. Starte mit drei getrennten Figurenklassen ohne gemeinsame Oberklasse.
  2. Identifiziere gleiche Methoden.
  3. Fuehre abstrakte GeometrischeFigur ein.
  4. Ziehe gemeinsame Methoden per Pull-up nach oben.
  5. Fuehre protected abstract void zeichnen(); ein.
  6. Teste polymorphe Aufrufe in einer Figurenliste.

So wird der Designweg nachvollziehbar statt nur das Endergebnis abgeschrieben.

Testmatrix fuer Vererbung

  1. Konstruktion jeder konkreten Subklasse.
  2. Aufruf geerbter Methoden auf Subklassenobjekten.
  3. Aufruf ueberschriebener Methoden ueber Oberklassenreferenz.
  4. Sichtbarkeitspruefung fuer private/protected.
  5. Sicherstellen, dass abstrakte Klassen nicht instanziiert werden.

Transfer in andere Domaenen

Das Figurenbeispiel laesst sich auf viele Kontexte uebertragen:

  • Fahrzeug -> Auto, Fahrrad, Bus
  • Dokument -> PDF, Textdatei, Praesentation
  • Mitarbeiter -> Lehrkraft, Verwaltung, IT

Die Leitfrage bleibt immer gleich: Welche Logik ist wirklich gemeinsam, welche ist fachlich spezifisch?

Kurzfazit zur Vertiefung

Das Arbeitsblatt vermittelt Vererbung als Modellierungswerkzeug mit Regeln:

  • fachliche Spezialisierung statt Codeklauberei
  • klare Trennung von Override und Overload
  • abstrakte Oberklasse fuer gemeinsame Struktur
  • Refactoring entlang der Hierarchie als normaler Entwicklungsprozess

Diese Perspektive verhindert viele spaetere Architekturprobleme.

Abschlussaufgabe zur Vererbung

Entwerfe eine kleine Hierarchie Dokument -> TextDokument, BildDokument, TabellenDokument.

Dokument soll abstrakt sein und enthalten:

  • gemeinsame Metadaten
  • abstrakte Methode render()
  • konkrete Methode druckeHeader()

Aufgaben:

  1. Implementiere alle Subklassen mit eigener render-Logik.
  2. Fuehre eine Liste vom Typ Dokument[] und rufe polymorph render auf.
  3. Erklaere, warum Dokument abstrakt sein sollte.

Damit wird das Figurenmuster in eine neue Domaene uebertragen und das Vererbungsverstaendnis gefestigt.