# 4 Vererbung, Polymorphie und Interfaces Wie eine Klasse definiert wird, haben Sie im letzten Kapitel gelernt. Nun geht es darum, sich mit einem anderen wichtigen Aspekt der Objektorientierung auseinanderzusetzen: der Vererbung. Bei näherer Betrachtung wird die Vererbung Fragen aufwerfen, die uns den Begriff der Polymorphie näherbringen. ## 4.1 Die Vererbung ### 4.1.1 Basisklassen und abgeleitete Klassen Welche Fähigkeit würden Sie von einem Circle-Objekt neben den bereits implementierten Fähigkeiten noch erwarten? Wahrscheinlich eine ganz wesentliche, nämlich die Fähigkeit, sich in einer beliebigen grafikfähigen Komponente visualisieren zu können. Bisher fehlt dazu noch eine passende Methode. Die Klasse Circle soll jedoch von uns als abgeschlossen betrachtet werden. Damit simulieren wir zwei Ausgangssituationen, die in der täglichen Praxis häufig auftreten: Die Implementierung einer Klasse, wie beispielsweise Circle, ist für viele Anwendungsfälle völlig ausreichend. Eine Erweiterung würde nicht allgemeinen, sondern nur speziellen Zusatzanforderungen genügen. Die Klasse liegt nur kompiliert vor. Damit besteht auch keine Möglichkeit, den Quellcode der Klasse um weitere Fähigkeiten zu ergänzen. Wie kann das Problem gelöst werden, eine Klasse um zusätzliche Fähigkeiten zu erweitern, damit sie weitergehenden Anforderungen gewachsen ist? Die Antwort ist sehr einfach und lautet: Es muss eine weitere Klasse entwickelt werden. Diese soll im weiteren Verlauf GraphicCircle heißen. Die zusätzliche Klasse soll einerseits alle Fähigkeiten der Klasse Circle haben und darüber hinaus auch noch eine Methode namens Draw, um das Objekt zu zeichnen. Mit der Vererbung, einem der Stützpfeiler der objektorientierten Programmierung, ist die Lösung sehr einfach zu realisieren. Eine Klasse, die ihre Member als Erbgut einer abgeleiteten Klasse zur Verfügung stellt, wird als Basisklasse bezeichnet. Die erbende Klasse ist die Subklasse oder einfach nur die abgeleitete Klasse. Dem Grundprinzip der Vererbung folgend, verfügen Subklassen normalerweise über mehr Funktionalitäten als ihre Basisklassen. Zwei Klassen, die miteinander in einer Vererbungsbeziehung stehen, werden (wie in Abbildung 4.1 gezeigt) durch einen Beziehungspfeil von der abgeleiteten Klasse in Richtung der Basisklasse dargestellt. Hier klicken, um das Bild zu vergrößern Abbildung 4.1 Die Vererbungsbeziehung zwischen den Klassen »Circle« und »GraphicCircle« Die Vererbungslinie ist nicht zwangsläufig mit dem Ableiten einer Klasse aus einer Basisklasse beendet. Eine Subklasse kann selbst zur Basisklasse mutieren, wenn von ihr wiederum Klassen abgeleitet werden. Es ist auch möglich, von einer Klasse mehrere Subklassen abzuleiten, die dann untereinander beziehungslos sind. Am Ende kann dadurch eine nahezu beliebig tiefe und weit verzweigte Vererbungshierarchie entstehen, die einer Baumstruktur ähnelt. Jeder Baum hat einen Stamm. Genauso sind auch alle Klassen von .NET auf eine allen gemeinsame Klasse zurückzuführen: Object. Diese Klasse ist auch die einzige in .NET, die selbst keine Basisklasse hat. Wenn Sie bei einer Klassendefinition ausdrücklich keine Basisklasse angeben, ist Object immer die direkte Basisklasse. Deshalb finden Sie in der Intellisense-Liste immer die Methoden Equals, GetType, ToString und GetHashCode. Prinzipiell wird in der Objektorientierung zwischen der Einfach- und der Mehrfachvererbung unterschieden. Bei der einfachen Vererbung hat eine Klasse nur eine direkte Basisklasse, bei der Mehrfachvererbung können es mehrere sein. Eine Klassenhierarchie, die auf Mehrfachvererbung basiert, ist komplex und kann unter Umständen zu unerwarteten Nebeneffekten führen. Um solchen Konflikten aus dem Weg zu gehen, wird die Mehrfachvererbung von .NET nicht unterstützt. Damit werden einerseits zwar bewusst Einschränkungen in Kauf genommen, die aber andererseits durch die Schnittstellen (Interfaces) nahezu gleichwertig ersetzt werden. Das Thema Interfaces wird uns im gleichnamigen Abschnitt 4.7 noch beschäftigen. ### 4.1.2 Ableitung einer Klasse Wenden wir uns nun wieder unserem Beispiel zu, und ergänzen wir das Projekt GeometricObjectsSolution um die Klasse GraphicCircle, die die Klasse Circle beerben soll. Zudem soll GraphicCircle um die typspezifische Methode Draw erweitert werden. Die Ableitung wird in der neuen Klassendefinition durch den Doppelpunkt und die sich daran anschließende Bekanntgabe der Basisklasse zum Ausdruck gebracht:
    public class GraphicCircle : Circle {
      public void Draw() {
        Console.WriteLine("Der Kreis wird gezeichnet");
      }
    }
Wir wollen an dieser Stelle das Kreisobjekt nicht wirklich zeichnen, sondern nur stellvertretend eine Zeichenfolge an der Konsole ausgeben. Die Konsequenz der Vererbung können Sie zu diesem Zeitpunkt bereits sehen, wenn Sie ein Objekt des Typs GraphicCircle mit
    GraphicCircle gc = new GraphicCircle();
erzeugen und danach die Punktnotation auf den Objektverweis anwenden: In der IntelliSense-Liste werden neben der neuen Methode Draw alle öffentlichen Mitglieder der Klasse Circle angezeigt, obwohl diese in der abgeleiteten Klasse nicht definiert sind (siehe Abbildung 4.2). Natürlich fehlen auch nicht die von Object geerbten Methoden. Hier klicken, um das Bild zu vergrößern Abbildung 4.2 Die von der Klasse »Circle« geerbten Fähigkeiten Die Tatsache, dass ein Objekt vom Typ GraphicCircle alle Komponenten der Klasse Circle offenlegt, lässt unweigerlich den Schluss zu, dass das Objekt einer abgeleiteten Klasse gleichzeitig auch ein Objekt der Basisklasse sein muss. Zwischen den beiden in der Vererbungshierarchie in Beziehung stehenden Klassen existiert eine Beziehung, die als Ist-ein(e)-Beziehung bezeichnet wird. Prägen Sie sich die folgende Regel gut ein:
Regel
Ein Objekt vom Typ einer abgeleiteten Klasse ist gleichzeitig auch immer ein Objekt vom Typ seiner Basisklasse.
Das bedeutet weitergehend auch, dass ein Objekt vom Typ GraphicCircle gleichzeitig ein Objekt vom Typ Object ist – so wie auch ein Circle-Objekt vom Typ Object ist. Letztendlich ist alles im .NET Framework vom Typ Object. Eine weitere wichtige Schlussfolgerung kann daraus auch gezogen werden: In Richtung der Basisklassen werden die Objekte immer allgemeiner, in Richtung der abgeleiteten Klassen immer spezialisierter. Dass es sich bei der Vererbung um die codierte Darstellung einer Ist-ein(e)-Beziehung handelt, sollten Sie sich gut einprägen. Es hilft dabei, Vererbungshierarchien sinnvoll und realitätsnah umzusetzen. Sie werden dann sicher nicht auf die Idee kommen, aus einem Elefanten eine Mücke abzuleiten, nur weil der Elefant vier Beine hat und eine Mücke derer sechs. Sie würden in dem Sinne zwar aus einer Mücke einen Elefanten machen, aber eine Mücke ist nicht gleichzeitig ein Elefant … ### 4.1.3 Klassen, die nicht abgeleitet werden können #### Klassen, die als »sealed« definiert sind Basisklassen vererben den Subklassen ihre Eigenschaften und Methoden. Es kommt aber immer wieder vor, dass die weitere Ableitung von einer Klasse keinen Sinn macht oder sogar strikt unterbunden werden muss, weil die von der Klasse zur Verfügung gestellten Dienste als endgültig betrachtet werden. Um sicherzustellen, dass von eine Klasse nicht weiter abgeleitet werden kann, wird die Klassendefinition um den Modifizierer sealed ergänzt:
    public sealed class GraphicCircle {
      ...
    }
#### Statische Klassen und Vererbung Neben sealed-Klassen sind auch statische Klassen nicht vererbungsfähig. Darüber hinaus dürfen statische Klassen auch nicht aus einer beliebigen Klasse abgeleitet werden. Die einzig mögliche Basisklasse ist Object. ### 4.1.4 Konstruktoren in abgeleiteten Klassen Bei der Erzeugung des Objekts einer Subklasse gelten dieselben Regeln wie beim Erzeugen des Objekts einer Basisklasse: Es wird generell ein Konstruktor aufgerufen. Die Subklassenkonstruktoren dürfen überladen werden. Konstruktoren werden grundsätzlich nicht von der Basisklasse an die Subklasse vererbt. Daher müssen alle notwendigen Konstruktoren in einer abgeleiteten Klasse definiert werden. Das gilt auch für den statischen Initialisierer. Abgesehen vom impliziten, parameterlosen Standardkonstruktor
    public GraphicCircle(){}
ist die Klasse GraphicCircle daher noch konstruktorlos. Um dem Anspruch zu genügen, einem Circle-Objekt auch hinsichtlich der Instanziierbarkeit gleichwertig zu sein, benötigen wir insgesamt drei Konstruktoren, die in der Lage sind, entweder den Radius oder den Radius samt der beiden Bezugspunktkoordinaten entgegenzunehmen. Außerdem müssen wir berücksichtigen, dass Objekte vom Typ GraphicCircle gleichzeitig Objekte vom Typ Circle sind. Die logische Konsequenz ist, den Objektzähler mit jedem neuen GraphicCircle-Objekt zu erhöhen. Mit diesen Vorgaben, die identisch mit denen in der Basisklasse sind, sieht der erste und, wie Sie noch sehen werden, etwas blauäugige Entwurf der Erstellungsroutinen in der Klasse GraphicCircle zunächst folgendermaßen aus:
    public class GraphicCircle : Circle {
      public GraphicCircle() {
        _CountCircles++;
      }
      public GraphicCircle(double radius) : this() {
        Radius = radius;
      }
      public GraphicCircle(double radius, int xPos, int yPos) : this(radius) {
        XCoordinate = xPos;
        YCoordinate = yPos;
      }
    }
Der Versuch, diesen Programmcode zu kompilieren, endet jedoch in einem Fiasko, denn der C#-Compiler kann das Feld _CountCircles nicht erkennen und verweigert deswegen die Kompilierung. Der Grund hierfür ist recht einfach: Das Feld ist in der Basisklasse Circle als private deklariert. Private Member sind aber grundsätzlich nur in der Klasse bekannt, in der sie deklariert sind. Obwohl aus objektorientierter Sicht ein Objekt vom Typ GraphicCircle auch gleichzeitig als ein Objekt vom Typ Circle angesehen wird, kann die strikte Kapselung einer privaten Variablen durch die Vererbung nicht aufgebrochen werden. Nur der Code in der Klasse Circle hat Zugriff auf die privaten Klassenmitglieder. ### 4.1.5 Der Zugriffsmodifizierer »protected« Einen Ausweg aus diesem Dilemma, ein Klassenmitglied einerseits gegen den unbefugten Zugriff von außen zu schützen, es aber andererseits in einer abgeleiteten Klasse sichtbar zu machen, bietet der Zugriffsmodifizierer protected. Member, die als protected deklariert sind, verhalten sich ähnlich wie als private deklarierte: Sie verhindern den unzulässigen Zugriff von außerhalb, garantieren jedoch andererseits, dass in einer abgeleiteten Klasse direkt darauf zugegriffen werden kann. Diese Erkenntnis führt zu einem Umdenken bei der Implementierung einer Klasse: Muss davon ausgegangen werden, dass die Klasse als Basisklasse ihre Dienste zur Verfügung stellt, sind alle privaten Member, die einer abgeleiteten Klasse zur Verfügung stehen sollen, als protected zu deklarieren. Daher müssen wir in der Klasse Circle noch folgende Änderungen vornehmen:
    // Änderung der Zugriffsmodifizierer der privaten Felder
    // in der Klasse Circle
    protected double _Radius;
    protected static int _CountCircles;
Erst jetzt ist die Klasse Circle tatsächlich vererbungsfähig, und der C#-Compiler wird keinen Fehler mehr melden. ### 4.1.6 Konstruktorverkettung in der Vererbung Wir wollen nun die Implementierung in Main testen, indem wir ein Objekt des Typs GraphicCircle erzeugen und uns den Stand des Objektzählers, der von der Circle-Klasse geerbt wird, an der Konsole ausgeben lassen. Der Code dazu lautet:
    static void Main(string[] args) {
      GraphicCircle gc = new GraphicCircle();
      Console.WriteLine("Anzahl der Kreise = {0}", GraphicCircle.CountCircles);
    }
Völlig unerwartet werden wir mit einer Situation konfrontiert, die wir nicht erwartet haben. Mit
    Anzahl der Kreise = 2
wird uns suggeriert, wir hätten zwei Kreisobjekte erzeugt, obwohl wir doch tatsächlich nur einmal den new-Operator benutzt haben und sich folgerichtig auch nur ein konkretes Objekt im Speicher befinden kann. Das Ergebnis ist falsch und beruht auf der bisher noch nicht berücksichtigten Aufrufverkettung zwischen den Sub- und den Basisklassenkonstruktoren. Konstruktoren werden bekanntlich nicht vererbt und müssen deshalb – falls erforderlich – in jeder abgeleiteten Klasse neu definiert werden. Dennoch kommt den Konstruktoren einer Basisklasse eine entscheidende Bedeutung zu. Bei der Initialisierung eines Subklassenobjekts wird nämlich in jedem Fall zuerst ein Basisklassenkonstruktor aufgerufen. Es kommt zu einer Top-down-Verkettung der Konstruktoren, angefangen bei der obersten Basisklasse (Object) bis hinunter zu der Klasse, deren Konstruktor aufgerufen wurde. Die Verkettung der Konstruktoraufrufe dient dazu, zunächst die geerbten Komponenten der Basisklasse zu initialisieren. Erst danach wird der Konstruktor der direkten Subklasse ausgeführt, der eigene Initialisierungen vornehmen kann und gegebenenfalls auch die Vorinitialisierung der geerbten Komponenten an die Bedürfnisse der abgeleiteten Klasse anpasst. Standardmäßig wird dabei immer zuerst der parameterlose Konstruktor der Basisklasse aufgerufen. Hier klicken, um das Bild zu vergrößern Abbildung 4.3 Die Verkettung der Konstruktoraufrufe in einer Vererbungshierarchie Die Konstruktorverkettung hat maßgeblichen Einfluss auf die Modellierung einer Klasse, die parametrisierte Konstruktoren enthält. Eine konstruktorlose Klasse hat grundsätzlich immer einen impliziten, parameterlosen Konstruktor. Ergänzt man jedoch eine Klasse um einen parametrisierten Konstruktor, existiert der implizite, parameterlose nicht mehr. Wird nun das Objekt einer ableitenden Klasse erzeugt, kommt es zum Aufruf des parameterlosen Konstruktors der Basisklasse. Wird dieser durch parametrisierte Konstruktoren überdeckt, meldet der Compiler einen Fehler. Sie sollten sich dessen bewusst sein, wenn Sie eine ableitbare Klasse entwickeln und parametrisierte Konstruktoren hinzufügen. Das Problem ist sehr einfach zu lösen, indem Sie einen parameterlosen Konstruktor ohne Programmcode in der Basisklasse definieren. #### Konstruktorverkettung mit »base« steuern Nun erklärt sich auch das scheinbar unsinnige Ergebnis des Objektzählers im vorhergehenden Abschnitt, der bei der Instanziierung eines Objekts vom Typ GraphicCircle behauptete, zwei Kreisobjekte würden vorliegen, obwohl es nachweislich nur ein einziges sein konnte. Durch die Konstruktorverkettung wird zunächst der parameterlose Konstruktor der Basisklasse Circle aufgerufen, danach der der Klasse GraphicCircle. In beiden wird der Objektzähler erhöht, was letztendlich zu einem falschen Zählerstand führt. Die Ursache des Problems ist die Duplizität der Implementierung der beiden parameterlosen Konstruktoren, nämlich in Circle:
    public Circle() {
      countCircles++;
    }
und in der von Circle abgeleiteten Klasse GraphicCircle:
    public GraphicCircle() {
      countCircles++;
    }
Betrachten wir auch noch einmal die Implementierung der anderen Konstruktoren in GraphicCircle:
    public GraphicCircle(double radius):this(){...}
    public GraphicCircle(double radius, int xPos, int yPos):this(radius){...}
Alle Aufrufe parametrisierter Konstruktoren werden derzeit mit this direkt oder indirekt an den parameterlosen Konstruktor weitergeleitet, der dann seinerseits den parameterlosen der Basisklasse Circle aufruft. Zudem entspricht der Code in GraphicCircle exakt dem Code in den gleich parametrisierten Konstruktoren in Circle. Es bietet sich an, anstelle des klassenintern weiterleitenden this-Aufrufs den Aufruf direkt an den Konstruktor der Basisklasse zu delegieren, dessen Parameterliste identisch ist. Dabei müssen die dem Konstruktor übergebenen Argumente an den gleich parametrisierten Konstruktor der Basisklasse übergeben werden. Um eine Methode in der Basisklasse aufzurufen (und als eine solche werden auch die Konstruktoren angesehen) bietet C# das Schlüsselwort base an, mit dem innerhalb einer abgeleiteten Klasse auf Mitglieder der direkten Basisklasse zugegriffen werden kann. Sie können beim Aufruf von base Argumente übergeben. Aufgrund von Anzahl und Typ der Argumente ist damit auch die aufzurufende Basisklassenmethode festgelegt. Das gilt gleichermaßen auch für die Konstruktoren. Da das objektorientierte Paradigma vorschreibt, dass aus einer Subklasse heraus mittels Aufrufverkettung immer zuerst ein Konstruktor der Basisklasse ausgeführt werden muss, haben wir die implizite Verkettung durch eine explizite ersetzt und die Steuerung selbst übernommen: Es kommt zu keinem weiteren impliziten Aufruf mehr, auch nicht an den parameterlosen Basisklassenkonstruktor. Sehen wir uns nun die überarbeitete Fassung der GraphicCircle-Konstruktoren an:
    public GraphicCircle() {}
    public GraphicCircle(double radius) : base(radius) {}
    public GraphicCircle(double radius, int xPos,
                         int yPos) : base(radius, xPos, yPos) {}
Beim parameterlosen Konstruktor können wir auf die base-Anweisung verzichten, weil der Aufruf auch ohne diese Angabe automatisch an den parameterlosen Basisklassenkonstruktor weitergeleitet wird. Schreiben wir jetzt eine Testroutine, z. B.
    GraphicCircle gc = new GraphicCircle();
    Console.WriteLine("Anzahl der Kreise = {0}",
          GraphicCircle.CountCircles);
dann wird die Ausgabe des Objektzählers tatsächlich den korrekten Stand wiedergeben. #### Zugriff auf Member der Basisklasse mit »base« Mit base kann nicht nur die Konstruktorenverkettung explizit gesteuert werden. Sie können dieses Schlüsselwort auch dazu benutzen, um innerhalb einer abgeleiteten Klasse auf Member der Basisklasse zuzugreifen, solange sie nicht als private deklariert sind. Dabei gilt, dass die Methode der Basisklasse, auf die zugegriffen wird, durchaus eine von dieser Klasse selbst geerbte Methode sein kann, also aus Sicht der Subklasse, die base implementiert, aus einer indirekten Basisklasse stammt, beispielsweise:
    class BaseClass {
      public void TestMethod() {
        Console.WriteLine("In 'BaseClass.TestMethod()'");
       }
    }
    class SubClass1 : BaseClass {}
    class SubClass2 : SubClass1 {
      public void BaseTest()    {
        base.TestMethod();
      }
    }
Ein umgeleiteter Aufruf an eine indirekte Basisklasse mit
    // unzulässiger Aufruf
    base.base.TestMethod();
ist nicht gestattet. Handelt es sich bei der über base aufgerufenen Methode um eine parametrisierte Methode, müssen den Parametern die entsprechenden Argumente übergeben werden.