Beginnen wir mit der zyklischen Abhängigkeit.
trait A {
selfA: B =>
def fa: Int }
trait B {
selfB: A =>
def fb: String }
Die Modularität dieser Lösung ist jedoch nicht so groß, wie es zunächst erscheinen mag, da Sie Selbsttypen wie folgt überschreiben können:
trait A1 extends A {
selfA1: B =>
override def fb = "B's String" }
trait B1 extends B {
selfB1: A =>
override def fa = "A's String" }
val myObj = new A1 with B1
Wenn Sie jedoch ein Mitglied eines Selbsttyps überschreiben, verlieren Sie den Zugriff auf das ursprüngliche Mitglied, auf das über Super-Vererbung weiterhin zugegriffen werden kann. Was also wirklich durch die Verwendung von Vererbung gewonnen wird, ist:
trait AB {
def fa: String
def fb: String }
trait A1 extends AB
{ override def fa = "A's String" }
trait B1 extends AB
{ override def fb = "B's String" }
val myObj = new A1 with B1
Jetzt kann ich nicht behaupten, alle Feinheiten des Kuchenmusters zu verstehen, aber es fällt mir auf, dass die Hauptmethode zur Durchsetzung der Modularität eher in der Komposition als in der Vererbung oder in den Selbsttypen besteht.
Die Vererbungsversion ist kürzer, aber der Hauptgrund, warum ich die Vererbung gegenüber Selbsttypen bevorzuge, ist, dass ich es viel schwieriger finde, die Initialisierungsreihenfolge bei Selbsttypen korrekt zu machen. Es gibt jedoch einige Dinge, die Sie mit Selbsttypen tun können, die Sie mit Vererbung nicht tun können. Selbsttypen können einen Typ verwenden, während für die Vererbung ein Merkmal oder eine Klasse wie folgt erforderlich ist:
trait Outer
{ type T1 }
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.
Sie können sogar tun:
trait TypeBuster
{ this: Int with String => }
Obwohl Sie es nie instanziieren können. Ich sehe keinen absoluten Grund dafür, nicht von einem Typ erben zu können, aber ich halte es auf jeden Fall für nützlich, Pfadkonstruktorklassen und -merkmale zu haben, da wir Typkonstruktormerkmale / -klassen haben. Da leider
trait InnerA extends Outer#Inner //Doesn't compile
Wir haben das:
trait Outer
{ trait Inner }
trait OuterA extends Outer
{ trait InnerA extends Inner }
trait OuterB extends Outer
{ trait InnerB extends Inner }
trait OuterFinal extends OuterA with OuterB
{ val myV = new InnerA with InnerB }
Oder dieses:
trait Outer
{ trait Inner }
trait InnerA
{this: Outer#Inner =>}
trait InnerB
{this: Outer#Inner =>}
trait OuterFinal extends Outer
{ val myVal = new InnerA with InnerB with Inner }
Ein Punkt, der mehr verstanden werden sollte, ist, dass Eigenschaften Klassen erweitern können. Vielen Dank an David Maclver für diesen Hinweis. Hier ist ein Beispiel aus meinem eigenen Code:
class ScnBase extends Frame
abstract class ScnVista[GT <: GeomBase[_ <: TypesD]](geomRI: GT) extends ScnBase with DescripHolder[GT] )
{ val geomR = geomRI }
trait EditScn[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
trait ScnVistaCyl[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
ScnBase
erbt von der Swing Frame-Klasse, sodass sie als Selbsttyp verwendet und am Ende (bei der Instanziierung) eingemischt werden kann. Allerdings val geomR
muss initialisiert werden , bevor es durch Vererbungseigenschaften verwendet wird . Wir brauchen also eine Klasse, um die vorherige Initialisierung von zu erzwingen geomR
. Die Klasse ScnVista
kann dann von mehreren orthogonalen Merkmalen geerbt werden, von denen selbst geerbt werden kann. Die Verwendung mehrerer Typparameter (Generika) bietet eine alternative Form der Modularität.