Commençons par la dépendance cyclique.
trait A {
selfA: B =>
def fa: Int }
trait B {
selfB: A =>
def fb: String }
Cependant, la modularité de cette solution n'est pas aussi grande qu'elle pourrait le paraître à première vue, car vous pouvez remplacer les auto-types comme suit:
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
Bien que, si vous remplacez un membre d'un auto-type, vous perdez l'accès au membre d'origine, qui peut toujours être consulté via super héritage. Donc, ce qui est vraiment gagné en utilisant l'héritage, c'est:
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
Maintenant, je ne peux pas prétendre comprendre toutes les subtilités du modèle de gâteau, mais il me semble que la principale méthode d'application de la modularité est la composition plutôt que l'héritage ou les types de soi.
La version d'héritage est plus courte, mais la principale raison pour laquelle je préfère l'héritage aux types self est que je trouve beaucoup plus difficile d'obtenir l'ordre d'initialisation correct avec les types self. Cependant, il y a certaines choses que vous pouvez faire avec les types d'individu que vous ne pouvez pas faire avec l'héritage. Les auto-types peuvent utiliser un type alors que l'héritage nécessite un trait ou une classe comme dans:
trait Outer
{ type T1 }
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.
Vous pouvez même faire:
trait TypeBuster
{ this: Int with String => }
Bien que vous ne puissiez jamais l'instancier. Je ne vois aucune raison absolue de ne pas pouvoir hériter d'un type, mais je pense certainement qu'il serait utile d'avoir des classes et des traits de constructeur de chemin comme nous avons des traits / classes de constructeur de type. Comme malheureusement
trait InnerA extends Outer#Inner //Doesn't compile
Nous avons ceci:
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 }
Ou ca:
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 }
Un point qui devrait être davantage souligné est que les traits peuvent étendre les classes. Merci à David Maclver de l'avoir signalé. Voici un exemple de mon propre 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
hérite de la classe Swing Frame, il peut donc être utilisé comme un auto-type puis mélangé à la fin (à l'instanciation). Cependant, val geomR
doit être initialisé avant d'être utilisé par l'héritage de traits. Nous avons donc besoin d'une classe pour appliquer l'initialisation préalable de geomR
. La classe ScnVista
peut alors être héritée de plusieurs traits orthogonaux qui peuvent eux-mêmes être hérités. L'utilisation de plusieurs paramètres de type (génériques) offre une autre forme de modularité.