Scala: Vererbung mit Traits

Posted by in Scala

Dieser Post ist eine Teilübersetzung von diesem exzellentem Beitrag von Joel Abrahamsson, der
hier zu finden ist.

Traits sind in Scala, was in Java Interfaces sind. Doch es gibt ein paar Besonderheiten, die sie flexibler und mächtiger machen als Interfaces in Java. Man kann sie sich am ehesten als abstrakte Klassen ohne Konstruktoren, aber mit der Möglichkeit Methoden vorzuimplementieren vorstellen. mit Java 8 steht dieses Feature auch in reinem Java zur Verfügung (siehe Default Methoden in Java 8).

Ein einfaches Vererbungsbeispiel: Vögel

Zuerst erstellen wir eine abstrakte Klasse Vogel mit einer Instanzvariable flugNachricht und zwei Methoden fliegen und schwimmen.

Die Instanzvariable ist abstrakt, deswegen muss man den Typ mit angeben, da er vom Compiler nicht automatisch ermittelt werden kann.

Zwei Implementierungen der Vogel-Klasse könnten sein.

Jetzt erzeugen wir ein bisschen Testcode und führen ihn aus

Als Ausgabe erhalten wir auf der Konsole wie erwartet:
Ich bin ein guter Flieger
Ich bin ein exzellenter Flieger

Der nicht fliegende Pinguin

Jetzt gibt es aber auch Vögel die nicht fliegen können: Pinguine und Strauße zum Beispiel. Wir könnten jetzt eine neue abstrakte Klasse FliegenderVogel erstellen, mit der Methode fliegen und die von Bird erbt. Die Klassen Moewe und Falke würden dann von FliegenderVogel erben und unser Pinguin nur von Bird. Alles Paletti. Das Klassendiagramm würde so aussehen.

Klassendiagramm Vögelhierarchie
image-12

Klassendiagramm Vögelhierarchie

Der Frigattvogel der nicht schwimmen kann

Dieser Vogel kann zwar fliegen, aber nicht schwimmen. Und hier fängt das Vererbungsdilemma an. Schön wäre es, wenn der Frigattvogel einfach von FliegenderVogel erben würde, aber dann hätte er auch die Methode schwimmen. Jetzt könnten wir eine neue abstrakte Klasse SchwimmenderVogel erstellen, in die man die schwimmen-Methode verschieben würde. Würden dann FliegenderVogel und Pinguin von SchwimmenderVogel erben, dann könnte der Frigattvogel aber nicht fliegen. In Java würde man jetzt einfach eine Interface erstellen, das die fly-Methode hätte, aber dann müsste die Implementierung in jeder Unterklasse die davon erbt verschoben werden. Wie man sieht, würde das DRY-Prinzip) verletzt werden und eine komplizierte Vererbungshierarchie zur Folge haben. Scala geht hier einen elegentaren Weg mit Traits.

Erstmal erstellen wir einen Trait Schwimmen für Vögel die Schwimmen können

Dann sieht die Bird Klasse ohne die schwimmen-Methode so aus

Damit Moewen und Falken immer noch fliegen können müssen sie jetzt noch den neuen Trait einbinden (engl. mix in)

Hier sieht man auch schön, wie Traits eingebunden werden müssen. Das Schlüsselwort with wird dabei verwendet, wenn es der zweite Trait ist, oder die Klasse bereits eine andere Klasse erweitert. Ein erster Trait/Klasse wird mit extends eingebunden/erweitert.

Jetzt können wir auch den Frigattvogel erstellen, der nicht weiß wie man schwimmt

Wie kriegen wir jetzt den Pinguin einfach unter? Richtig! Wir verlagern die fliegen-Methode ebenfalls in einen extra Trait. Damit schrumpft unser Bird auf ein Minimum und dient praktisch nur noch als Marker-Klasse

Fertig ist die komplette Hierarchie

Also lassen wir mal alle Vögel fliegen, die fliegen können und alle die schwimmen können schwimmen

Falls wir aber doch durch eine Laune der Natur einen Pinguin haben, der fliegen kann, dann würde uns der Compiler einen Fehler ausspucken „value fly is not a member of this.Bird“. Für ihn enthält die Liste fliegendeVoegel nämlich nur Vogel-Instanzen, denn das ist der kleinste gemeinsame Nenner aller Objekte.

Einbinden von Traits bei der Erstellung von Objekten

Wenn wir jetzt den Schwimmen-Trait von unserer Moewe-Klasse entfernen würden und dann die schwimmen-Methode aufrufen würden, gäbe es natürlich einen Compiler-Fehler.

error: value schwimmen is not a member of this.Moewe
moewe.schwimmen()
^
one error found

Das könnten wir natürlich beheben, indem wir unserer Moewe den Schwimmen-Trait einbinden lassen, oder aber bei der Erstellung einer Moewe-Instanz ihr direkt den Trait mitgeben.

Das funktioniert ohne Compiler-Gemecker.

Wenn wir das Gleiche mit dem Fliegen-Trait versuchen wollen, dann müssen wir beachten, dass die fliegen-Methode die abstrakte Instanzvariable flugNachricht benötigt. Der folgende Code ohne diese Definition kann nicht kompiliert werden

Zusammenfassung

Traits in Scala bieten einem die Möglichkeit von multipler Vererbung an. Für mich als Java-Programmierer hört sich das zuerst einmal gefährlich an, aber das Konzept ist so programmiert, dass die gefürchteten Nachteile die man zum Beispiel aus C++ kennt nicht wirklich auftreten können. In Java muss man sich entweder mit komplexen Vererbungshierachien herumschlagen oder Aspekt-Orientiertes-Programmieren einsetzen, um das gleiche zu erreichen, wie hier mit dem Einbinden eines Traits.

Zwei weiterführende Links (auf Englisch) habe ich auch noch:

A Tour of Scala: Traits
A Tour of Scala: Mixin Class Composition