2010-01-15 31 views
7

Supponiamo che io ho il seguente codice:contrastanti tratti ereditari annidati

trait Trait1 { 
    trait Inner { 
    val name = "Inner1" 
    } 
} 

trait Trait2 { 
    trait Inner { 
    val name = "Inner2" 
    } 
} 

class Foo extends Trait1 with Trait2 { 
    // I want Concrete1 to be a Trait1.Inner not a Trait2.Inner 
    class Concrete1 extends Inner 
    val c = new Concrete1 
} 

object Obj { 
    def main(args: Array[String]): Unit = { 
    val foo = new Foo 
    println(foo.c.name) 
    } 
} 

Quando mescolo in Trait1 e Trait2, riferendosi a Inner sembra di default al Inner tipo di qualsiasi caratteristica I mixin secondo; quindi quando chiamo il metodo mainmain, stampa Inner2. Come posso fare riferimento a Trait1.Inner in Foo? Tutti e tre i seguenti errori del compilatore sono riportati:

class Concrete1 extends Trait1.Inner 
class Concrete1 extends Trait1$Inner 
class Concrete1 extends Trait1#Inner 

risposta

6

Invece di

class Concrete1 extends Inner 

Utilizzare questa

class Concrete1 extends super[Trait1].Inner 

Questo dovrebbe prenditi quello che vuoi vuoi

+0

È grandioso, ora posso scrivere la classe Concrete1 estende super [Trait1] .Inner e class Concrete2 estende super [Trait2] .Inner e hanno istanze di entrambi i tipi di Inner in Foo. – ams

2

Perché non ordinare i tratti nell'ordine che ci si aspetta abbiano la precedenza? La linearizzazione dei tratti non è arbitraria, ma specificata.

+0

Sì, avevo pensato di farlo. Ma supponiamo che voglio creare un Concrete2 estende anche Trait2.Inner. Cosa poi? – ams

4

Ci sono due spazi dei nomi all'interno di un template (modello essendo il corpo di una classe, un oggetto, o un tratto.)

  1. Utenti: Vals, VAR, e defs e oggetti nidificati
  2. tipi: tipi alias , tratti nidificati e classi nidificate

Quando si ereditano da più modelli padre, i conflitti in questi spazi dei nomi vengono risolti attraverso la linearizzazione della classe.

È possibile riordinare l'eredità per portare il genitore interno desiderato nella classe o trovare un progetto alternativo.

+0

Grazie, non mi è venuto in mente che i tratti dei membri sarebbero stati sovrascritti. Ha senso quando ci pensi! – ams

3

Un'opzione (se si può essere invasivi per i tratti) consiste nel definire ogni tratto interno come membro del tipo che ha un nome non in conflitto.

trait Trait1 { 
    type Inner1 = Inner 
    trait Inner { 
    val name = "Inner1" 
    } 
} 

trait Trait2 { 
    type Inner2 = Inner 
    trait Inner { 
    val name = "Inner2" 
    } 
} 

class Foo extends Trait1 with Trait2 { 
    class Concrete1 extends Inner1 
    class Concrete2 extends Inner2 
    val c1 = new Concrete1 
    val c2 = new Concrete2 
} 

object App extends Application { 
    val foo = new Foo 
    println(foo.c1.name) // Inner1 
    println(foo.c2.name) // Inner2 
} 

Se non si può essere invasiva per i tratti originali (Trait1 e Trait2), è possibile estendere loro di definire il membro tipo.

trait Trait1 { 
    trait Inner { 
    val name = "Inner1" 
    } 
} 
trait Trait2 { 
    trait Inner { 
    val name = "Inner2" 
    } 
} 

trait Trait1a extends Trait1 { 
    type Inner1 = Inner 
} 
trait Trait2a extends Trait2 { 
    type Inner2 = Inner 
} 

class Foo extends Trait1a with Trait2a { 
    class Concrete1 extends Inner1 
    class Concrete2 extends Inner2 
    val c1 = new Concrete1 
    val c2 = new Concrete2 
} 

Un altro approccio sarebbe quello di utilizzare un tratto intermedio per definire la vostra prima classe concreta:

trait Trait1 { 
    trait Inner { 
    val name = "Inner1" 
    } 
} 
trait Trait2 { 
    trait Inner { 
    val name = "Inner2" 
    } 
} 

trait FooIntermediate extends Trait1 { 
    class Concrete1 extends Inner 
} 

class Foo extends FooIntermediate with Trait2 { 
    class Concrete2 extends Inner 
    val c1 = new Concrete1 
    val c2 = new Concrete2 
} 
+0

Grazie, ho pensato di fare il tipo Inner1 = Inner. Non avevo pensato alle tue altre due opzioni; ora ho troppe scelte :) – ams