2013-04-05 14 views
5

mi è capitato di scoprire che non è permesso avere campi privati ​​astratti in un tratto, cioè,astratti campi privati ​​a Scala tratto

trait A1 { 
    //private val a: Int   // Not allowed 
    protected val b: Int   // OK 
} 

e sembra tutto a posto per fare cosa simile ad una classe astratta , se i campi privati ​​ sono parametri del costruttore, cioè,

abstract class A2 (private val i: Int) // OK 

quindi credo che un tratto non ha parametri del costruttore, quindi non c'è modo per inizializzare loro, quindi non fie privato astratto lds sono ammessi.

Se sono "protetti", una sottoclasse può inizializzarli utilizzando i campi pre-inizializzati . Questo approccio consente alla sottoclasse di vedere questi campi.

E se volessi inizializzarli e nasconderli in seguito, come nell'esempio seguente?

object holding { 
    trait trick { 
     protected val seed: Int      // Can't be private 
     final def magic: Int = seed + 123 
    } 

    trait new_trick extends trick { 
     def new_magic: Int = magic + 456 
     def the_seed: Int = seed     // [1] 
    } 

    def play: new_trick = new { val seed = 1 } with new_trick 

    def show_seed(t: new_trick): Int = t.the_seed // [2] 
} 

Io Non voglia a chiunque di essere in grado di vedere seme, cioè, [2] (e quindi [1]) non dovrebbe essere consentito. C'è un modo per un tratto per farlo?


Come @Randall e @ pagoda_5b hanno fatto notare, la mia domanda non ha molto senso . Ma fortunatamente @ Régis e @ axel22 l'hanno trasformato in un'altra interessante domanda e fornito un modello per risolverlo.

+1

Come può essere significativo avere qualcosa di privato e non implementato? È una contraddizione, dal momento che non è ereditata e non può mai essere implementata e quindi impedirebbe l'istanziazione di qualsiasi sottotipo del tratto che porta tale membro. –

+1

Non sono sicuro di seguirti. Se hai bisogno di sottoclassi per definire il seme (ignorando la sua definizione), non ha senso renderlo privato. Detto questo, la sottoclasse che definisce l'implementazione del membro di seed sarà sempre in grado di mostrarla con un accesso pubblico. Non riesco a capire la necessità di questa scelta progettuale. –

+0

Penso che si riduca ad essere in grado di lasciare che il sub-tratto diretto definisca il valore di 'seed' mentre rende ulteriori sotto-tratti (e codice esterno) non in grado di accedere al valore. Un po 'come un valore protetto (trattare come un valore protetto per i sotto-tratti e trattare come privato per qualsiasi cosa). Per quanto riguarda la significatività, penso che l'idea sia solo quella di emulare completamente ciò che può essere fatto attraverso i parametri (nelle classi) come mostrato nel suo esempio. –

risposta

8

Un modo semplice per mantenere privato un val mentre consente ai sotto-tratti di inizializzarlo sarebbe definirlo come privato ma inizializzarlo con il valore restituito da un altro metodo protetto. Quindi i sotto-tratti possono definire questo metodo protetto in modo da modificare il valore iniziale, ma non possono accedere al valore stesso. Così si avrebbe cambiato questo:

trait A { 
    protected val foo: Bar 
} 

in:

trait A { 
    private val foo: Bar = initFoo 
    protected def initFoo: Bar 
} 

Ora, trait solo A può accedere alla val foo. Sub-tratti possono impostare il valore iniziale di foo da definint initFoo, ma non possono accedere foo stessa:

trait B extends A { 
    protected def initFoo: Bar = ??? 
} 

Ovviamente, initFoo sé è ancora accessibile da sotto-tratti. Questo spesso non è un problema se initFoo crea una nuova istanza ogni volta (in altre parole, è una factory), in quanto potremmo essere interessati a rendere l'istanza privata a A senza preoccuparsi di avere i sotto-tratti che sono in grado di creare nuove istanze di Bar (indipendentemente dal fatto che le nuove istanze siano uguali a foo come da loro metodo equals).

Ma se si tratta di una preoccupazione (e certamente è nel tuo caso, come seed è fo tipo Int e, quindi, ciò che si desidera nascondere è un valore e non solo un riferimento), possiamo usare un trucco supplementare per consentire sotto-tratti per definire initFoo ma impedire loro (e i loro sotto-tratti) di poterlo chiamare. Questo trucco è, diciamocelo, piuttosto orribile per un'esigenza così semplice, ma illustra un buon modello per il controllo avanzato degli accessi. I crediti vanno agli autori delle librerie standard per l'idea (vedere http://www.scala-lang.org/api/current/index.html#scala.concurrent.CanAwait).

trait A { 
    // A "permit" to call fooInit. Only this instance can instantiate InitA 
    abstract class InitA private[this]() 
    // Unique "permit" 
    private implicit def initA: InitA = null 

    private def foo: Int = fooInit 
    protected def fooInit(implicit init: InitA): Int 
} 

trait B extends A { 
    protected def fooInit(implicit init: InitA): Int = 123 
} 

Ora, se B tenta di chiamare initFoo, il compilatore si lamenta che non si riusciva a trovare un implicito di tipo InitA (l'unico caso del genere è A.initA ed è solo accessibile al A).

Come ho detto, è un po 'orribile e la soluzione privata del pacchetto fornita da axel22 è sicuramente un'alternativa molto più semplice (anche se non impedirà a nessuno di definire i loro sotto-tratti all'interno dello stesso pacchetto del tuo, sconfiggendo così il restrizione di accesso).

+0

"Ora, se B prova a chiamare foo, il compilatore ...", intendevi "se B prova a chiamare fooInit" ? – cfchou

+0

Sì, certo, questo è quello che intendevo, grazie per la correzione. –

6

Il meglio che si possa fare è dichiarare privato questo pacchetto - private[package_name]. Ciò consentirebbe di estendere e definire il tratto all'interno dello stesso pacchetto in cui si sta eseguendo l'implementazione, ma non consentire ai client di utilizzarlo da altri pacchetti.

Problemi correlati