Gleich und doch nicht ganz: Polymorphismen und Überladung

Wer (mindestens) ein kleines Kind zu Hause hat, kennt vielleicht dieses Phänomen: An einem Tag steht die Spielküche noch in der einen Ecke und schon am nächsten Tag muss das komplette Zimmer umgeräumt werden, wobei natürlich nicht nur die Spielküche verrückt wird, sondern möglichst alle nur irgendwie beweglichen Möbel. Auch wenn sich das Aussehen des Zimmers deutlich verändert hat, ist es dennoch noch eindeutig als Kinderzimmer erkennbar.
Zugegeben, dieser Vergleich „hinkt“ (wie die meisten Vergleiche) ein wenig, denn eigentlich müsste es heißen „an einem Tag betreten sie das Zimmer durch die Tür während sie am nächsten Tag durch das Fenster hineinklettern“. Die Grundidee wird aber trotzdem klar – das Kinderzimmer unterliegt dem Konzept des Polymorphismus.

Beim Stöbern habe ich einen Artikel gefunden, in dem die wichtigsten Eigenschaften und Formen von Polymorphismus beschrieben werden. Grundsätzlich lässt sich aber sagen, dass polymorpher Code immer dort eingesetzt wird, wo gleiche oder ähnliche Codestrukturen an Variationen während der Laufzeit angepasst werden müssen- dazu können gehören:

  • Unterschiedliche Laufzeitumgebungen und Interpreter – im Rahmen von Java Script, z.B. unterschiedliche Browser
  • Unterschiedliche Argumente (z.B. bei Methoden), deren vorherige Umwandlung zu aufwändig oder schlicht nicht möglich wären
  • Beim Aufruf z.B. einer Methode unterschiedliche Rückgabewerte notwendig sind, die ebenfalls nicht einfach ineinander umgewandelt werden können
  • bei z.B. vererbten Klassen, eine Kindklasse andere Operationen beim Aufrufen einer Methode ausführen muss als die Elternklasse

Wer häufig Anwendungen auf Basis des .net-Frameworks von Microsoft entwickelt, dem ist dieses Konzept – in Form von sogenannten Überladungen – mit ziemlicher Sicherheit bereits begegnet.

//version 1
public int GetSum(int val1, int val2){

   return val1 + val2;

}

//version 2
public int GetSum(int[] values){

    int result = 0;

    foreach(int val in values){

        result += val;

    }

    return result;

}

//version 3
public string GetSum(string val1, string val2){

    int result = int.Parse(val1) + int.Parse(val2);

    return result.ToString();

}

Werden Methoden überladen (wie in diesem Beispiel Version 1 als initiale Version der Methode GetSum()),  so besitzen sie in der Regel den gleichen Namen, aber werden z.B. mit anderen Argumenten (Version 2) aufgerufen, oder/ und geben andere Datentypen zurück (Version 3).

Das Überschreiben von Methoden ist eine Technik, die vorwiegend im Zusammenhang mit Klassenvererbung eingesetzt wird. Eine ausführliche Beschreibung bei der Programmierung in JAVA liefert dieser Artikel, für C#.net würde ein Codebeispiel folgendermaßen aussehen:

//die Eltern-Klasse
public class ParentClass{

    public virtual void DoSomething(){

        MessageBox.Show(DateTime.Now.ToString());

    }

}

//die Kind-Klasse
public class ChildClass : ParentClass{

    public override void DoSomething(){

        MessageBox.Show("Hello World!");

    }

}

Auch hierbei wird der Name der Methode DoSomething(), aus der Eltern-Klasse, in der Kind-Klasse wiederverwendet. Diesmal wird jedoch die Methode in der Elternklasse als virtual definiert (womit sie überschrieben werden darf) und in der Kind-Klasse mit override dekoriert (wobei klar wird, dass damit die Methode aus der Eltern-Klasse „ersetzt“ wird).

Eine besondere Form des Polymorphismus hat sich für JavaScript herausgebildet: Neben der Verwendung der bereits beschriebenen Überladungsmethoden,  ist es häufig zusätzlich notwendig den verwendeten Browser für die Durchführung von Aufgaben zu berücksichtigen. Während in einigen Webbrowsern identische Methoden unterschiedliche Namen tragen, gibt es in einigen Browsern Eigenschaften oder Methoden, die ein Anderer gar nicht kennt. Ein Beispiel dafür ist document.documentMode in Microsofts Internet Explorer:

if (document.documentMode == 7) {
    doSomethingIn.IE7_only;
} else {
    doSomething.everwhereElse;
}

Häufig wird die Prüfung auf derartige Methoden verwendet um die korrekte Version des Browsers zu bestimmen und entsprechende, spezifische Operationen durchzuführen.
Gleichzeitig ist es aber auch möglich eigene Implementierungen für Methoden mitzugeben, die in einem Browser gar nicht nativ unterstützt werden. Diese eigenen Lösungen werden als sogenannte Polyfills bezeichnet und ein Beispiel dafür wäre die string.startsWith()-Methode, die vom Internet Explorer erst seit Version 12 nativ unterstützt wird:

//add the 'string.startsWith()' function (polyfill) (e.g. for the Internet Explorer)
if (!String.prototype.startsWith) {
  String.prototype.startsWith = function(searchString, position) {
    position = position || 0;
    return this.indexOf(searchString, position) === position;
  };
}

Gerade vor dem Hintergrund der verschiedenen Clean Code-Initiativen, gibt es immer wieder heftige Diskussionen zur Verwendung von polymorphem Code – und besonders zur Vererbung von Klassen und deren Methoden. Auch wenn ich viele der Argumente dagegen nachvollziehen kann, haben mir diese Techniken bereits in einer Vielzahl von Projekten gute Dienste geleistet. Ziel sollte es immer sein möglichst aufgeräumten und nachvollziehbaren Code zu produzieren. Warum aber sollte es besser sein, die Wand zum Kinderzimmer erneut durchzubrechen und eine zweite Tür hineinzumachen, als ggf. durch das Fenster herein zu klettern?