Zacznijmy od zależności cyklicznej.
trait A {
selfA: B =>
def fa: Int }
trait B {
selfB: A =>
def fb: String }
Jednak modułowość tego rozwiązania nie jest tak duża, jak mogłoby się wydawać na początku, ponieważ można zastąpić typy własne w następujący sposób:
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
Chociaż, jeśli zastąpisz członka typu własnego, utracisz dostęp do oryginalnego członka, do którego nadal można uzyskać dostęp poprzez super używanie dziedziczenia. Tak więc to, co naprawdę zyskuje się dzięki dziedziczeniu, to:
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
Teraz nie mogę twierdzić, że rozumiem wszystkie subtelności wzoru ciasta, ale uderza mnie to, że główną metodą egzekwowania modułowości jest kompozycja, a nie dziedziczenie lub samokształcenie.
Wersja dziedziczenia jest krótsza, ale głównym powodem, dla którego wolę dziedziczenie nad typami własnymi, jest to, że o wiele trudniej jest uzyskać prawidłową kolejność inicjowania z typami własnymi. Są jednak pewne rzeczy, które możesz zrobić z typami siebie, których nie możesz zrobić z dziedziczeniem. Typy własne mogą używać typu, podczas gdy dziedziczenie wymaga cechy lub klasy, jak w:
trait Outer
{ type T1 }
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.
Możesz nawet zrobić:
trait TypeBuster
{ this: Int with String => }
Chociaż nigdy nie będziesz w stanie tego zrobić. Nie widzę żadnego bezwzględnego powodu, aby nie móc dziedziczyć po typie, ale z pewnością uważam, że użyteczne byłoby posiadanie klas i cech konstruktora ścieżki, ponieważ mamy cechy / klasy konstruktora typów. Jak niestety
trait InnerA extends Outer#Inner //Doesn't compile
Mamy to:
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 }
Albo to:
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 }
Jednym z punktów, który należy bardziej wczuć w empatię, jest to, że cechy mogą rozszerzać klasy. Dziękujemy Davidowi Maclverowi za zwrócenie na to uwagi. Oto przykład z mojego własnego kodu:
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
dziedziczy z klasy Swing Frame, więc może być używany jako własny typ, a następnie dodawany na końcu (w instancji). Jednak val geomR
musi zostać zainicjowany, zanim zostanie wykorzystany przez dziedziczenie cech. Potrzebujemy więc klasy, aby wymusić wcześniejszą inicjalizację geomR
. Klasa ScnVista
może być następnie odziedziczona po wielu cechach ortogonalnych, z których można odziedziczyć. Korzystanie z wielu typów parametrów (generycznych) oferuje alternatywną formę modułowości.