class Test(val x: Int)
Ganz klar: hochkomplizierter Code.
Test
quillt an Features nur so über, aber uns fehlt gerade eine Methode damitwir Objekte dieser Klasse in unserem Code nahtlos verwenden können. Diese Methode nennen wir
bla
. Das Problem ist allerdings: Wir haben den Code von Test
nicht und können ihn deswegen nicht um bla erweitern.Jetzt gibt es verschiedene Wege wie wir diese Methode in
Test
einbinden könnten. Der offensichtlichste ist sicherlich Vererbung. Blöd ist nur: Wir haben bla
schon geschrieben und würden ihn ungern duplizieren, vor allem weil wir den Code bereits schön generalisiert haben.Eine Lösung ist hier ziemlich elegant mit Traits, Selftyping und Structural Types zu machen.
Als erstes definieren wir einen Structural Type der eine genaue Schnittstelle definiert. Wir nennen ihn
xAble
und er macht nichts weiter als zu definieren das alle Klassen die eine Methode x
anbieten vom Typ xAble
sind.
type xAble = { def x: Int }
Was wir jetzt brauchen ist unsere generalisierte Implementation von
bla
. Hier verwenden wir, Selftyping. Das ist ein Feature das es uns erlaubt innerhalb eines Traits so zu tun als wäre es von einem bestimmten Typ ohne davon zu erben.
trait Gimme[A <: xAble] {
self: A =>
def bla(): Unit = println(self.x + 1)
}
Wie man sieht muss der muss der Typparameter
A
ein Subtyp von xAble
sein, also die methode x
liefern.Alles was wir jetzt noch machen müssen ist das Trait
Gimme
in das Objekt des Typs Test
zu mischen und wir haben das ganze um bla
erweitert.
object Main {
class Test(val x: Int)
type xAble = { def x: Int }
trait Gimme[A <: xAble] {
self: A =>
def bla(): Unit = println(self.x + 1)
}
def main(args: Array[String]): Unit = {
val t = new Test(666) with Gimme[Test]
t.bla()
}
}
Das ganze Zeit mal wieder wie extrem mächtig das Scala Typsystem ist und was für elegante Features die Sprache zum lösen immer wiederkehrender Probleme bietet. Ich hoffe dieses Beispiel gibt euch eine kleine Idee was man damit machen kann. ;)