2009-12-02 3 views
27

Perché l'errore è sotto? Come risolverlo?Perché una classe non può estendere tratti con il metodo della stessa firma?

EDIT: Ho assunto che dal momento che A e B compili alle coppie (di interfaccia, di classe), si tratta di scegliere la giusta chiamata al metodo statico da implementare durante la compilazione di C. Mi aspetto che la priorità sia secondo l'ordine.

 
scala> trait A {def hi = println("A")} 
defined trait A 

scala> trait B {def hi = println("B")} 
defined trait B 

scala> class C extends B with A 
:6: error: error overriding method hi in trait B of type => Unit; 
method hi in trait A of type => Unit needs `override' modifier 
     class C extends B with A 

scala> trait A {override def hi = println("A")} 
:4: error: method hi overrides nothing 
     trait A {override def hi = println("A")} 

EDIT: Si noti che in Ruby questo funziona bene:

 
>> module B; def hi; puts 'B'; end; end 
=> nil 
>> module A; def hi; puts 'A'; end; end 
=> nil 
>> class C; include A; include B; end 
=> C 
>> c = C.new 
=> # 
>> c.hi 
B 
=> nil 

risposta

51

questo funziona per me in 2.8 e 2.11, e permetterebbe di essere non intrusivo in tratti A o B:

trait A { def hi = println("A") } 
trait B { def hi = println("B") } 

class C extends A with B { 
    override def hi = super[B].hi 
    def howdy = super[A].hi // if you still want A#hi available 
} 

object App extends Application { 
    (new C).hi // prints "B" 
} 
+3

Eccellente! Peccato che se provo 'la classe C estende A con B', l'errore non menziona questo modo di risolvere il conflitto. – IttayD

+0

@IttayD Con Scala 2.10.4: Il messaggio di errore dice come risolvere questo: (Nota: questo può essere risolto dichiarando un override nella classe C.) la classe C estende A con B { –

12

Si potrebbe utilizzare un tratto di base comune, dire Base, come segue:

trait Base {def hi: Unit} 
trait A extends Base {override def hi = println("A")} 
trait B extends Base {override def hi = println("B")} 
class C extends A with B 

Con la gerarchia dei tipi del il risultato della chiamata di hi è il seguente (notare l'uso di {} per istanziare i tratti):

scala> (new A {}).hi 
A 

scala> (new B {}).hi 
B 

scala> (new C).hi 
B 
+0

Grazie. Speravo in una soluzione non intrusiva. Qualcosa che può essere fatto in C da solo (come se A e B provengano da librerie di terze parti diverse) – IttayD

1

Questo è il diamond problem. Quale metodo hi dovrebbe essere ereditato, quello da A o quello da B? Puoi aggirare questo come suggerito da Don, usando un tratto base comune.

+0

Vedere il mio esempio Ruby, dove tutto è OK. Si tratta di decidere la semantica di "super". Posso suggerire che nell'albero dei tratti estesi sia selezionato il primo DFS saggio. Nota anche qui non c'è diamante. Solo A, B, C. Al contrario, la soluzione è creare un tratto Base, creando così un diamante.Quindi un diamante è una soluzione, non è un problema qui – IttayD

+4

Non c'è alcun problema di diamante in Scala, a causa della [linearizzazione della classe] (http://jim-mcbeath.blogspot.com/2009/08/scala-class-linearization.html) ... – paradigmatic

4

Un tratto aggiunge metodi alla classe che lo mescola. Se due tratti aggiungono lo stesso metodo, la classe finirebbe con due metodi identici, il che, ovviamente, non può avvenire.

Se il metodo è privato nel tratto, tuttavia, non causerà problemi. E se vuoi che i metodi si impilino uno sull'altro, puoi definire una caratteristica di base e poi abstract override sui tratti ereditari. Tuttavia, richiede una classe per definire il metodo. Ecco un esempio di questo:

scala> trait Hi { def hi: Unit } 
defined trait Hi 

scala> trait A extends Hi { abstract override def hi = { println("A"); super.hi } } 
defined trait A 

scala> trait B extends Hi { abstract override def hi = { println("B"); super.hi } } 
defined trait B 

scala> class NoHi extends Hi { def hi =() } 
defined class NoHi 

scala> class C extends NoHi with B with A 
defined class C 

scala> new C().hi 
A 
B 

Se, invece, si vuole veramente due metodi separati da ogni tratto, allora avrete bisogno di comporre invece di Eredita.

+0

Perché non basta che il compilatore selezioni un metodo come quello di implementazione? Poiché A e B sono compilati per interfacciare A e classe A $ con metodi statici, quindi nella JVM è perfettamente corretto che C implementa sia A che B e l'implementazione del metodo hi in C può semplicemente chiamare A $ class .hi – IttayD

+0

Nota che nella tua risposta, C finisce con due (anche tre) metodi identici, quindi questa cosa può essere fatta. – IttayD

+1

Se uno fosse disposto a gettare le basi concettuali per l'ereditarietà, sì, si potrebbe fare - e potrebbe essere rimpianto in seguito. Se vuoi la composizione, componi, non ereditare. –

0

Ho avuto lo stesso problema e non mi piaceva avere per creare un tratto intermedio perché posso avere 4,5 o addirittura 6 tratti con gli stessi metodi, perché sono tratti che contengono operazioni CRUD (trova, crea ...). Inoltre avevo bisogno di usare quei tratti insieme solo per scopi di test e cerco sempre di evitare il più possibile di modificare la struttura del mio progetto solo per rendere più facile il mio test. Così ho semplicemente implementato quei tratti in diversi oggetti:

class somethingToTest { 
    object AImpl extends ATrait 
    object BImpl extends BTrait 

    val a = AImpl.methodDuplicated() 
    val b = BImpl.methodDuplicated() 
} 

non è probabilmente il modo più intelligente di utilizzare i tratti, ma non richiede alcun cambiamento nel codice del progetto, implica solo per avere un po 'di più codice nei test.