2012-03-08 3 views
18

Sto ancora cercando di imparare il motivo a torta di Scala. Mi sembra che ti dia il vantaggio di centralizzare la configurazione di "Componenti" e la possibilità di fornire implementazioni predefinite per quei componenti (che sono ovviamente sovrascrivibili).Scala Cake Pattern Incoraggiare dipendenze hardcoded?

Tuttavia, l'uso di tratti di tipo self-type per descrivere le dipendenze sembra combinare aree di interesse. Lo scopo della Componente (credo) è quello di astrarre le diverse implementazioni per quel componente. Ma l'elenco delle dipendenze descritto nella Componente è di per sé un'implementazione.

Per esempio, diciamo che ho un database completo di widget, un Registro di sistema che mi permette di cercare specifici tipi di widget, e una sorta di algoritmo che utilizza il Registro di sistema per elaborare i widget:

case class Widget(id: Int, name:String) 

trait DatabaseComponent { 
    def database: (Int => Widget) = new DefaultDatabase() 

    class DefaultDatabase extends (Int => Widget) { 
    // silly impl 
    def apply(x: Int) = new Person(x, "Bob") 
    } 
} 

trait RegistryComponent { 
    this: DatabaseComponent => // registry depends on the database 

    def registry: (List[Int] => List[Widget]) = new DefaultRegistry() 

    class DefaultRegistry extends (List[Int] => List[Widget]) { 
    def apply(xs: List[Int]) = xs.map(database(_)) 
    } 
} 

trait AlgorithmComponent { 
    this: RegistryComponent => // algorithm depends on the registry 

    def algorithm: (() => List[Widget]) = new DefaultAlgorithm() 

    class DefaultAlgorithm extends (() => List[Widget]) { 
    // look up employee id's somehow, then feed them 
    // to the registry for lookup 
    def apply: List[Widget] = registry(List(1,2,3)) 
    } 
} 

E ora si può mettere insieme in qualche config centrale:

object Main { 
    def main(args: Array[String]) { 
    val algorithm = new AlgorithmComponent() with RegistryComponent with DatabaseComponent 

    val widgets = println("results: " + algorithm.processor().mkString(", ")) 
    } 
} 

Se voglio cambiare a un database diverso, mi può iniettare facilmente cambiando il mio mixin:

val algorithm = new AlgorithmComponent() with RegistryComponent with SomeOtherDatabaseComponent 


Ma ... cosa succede se voglio mescolare un componente del Registro diverso che non utilizza un database?

Se si tenta di eseguire la sottoclasse di RegistryComponent con un'implementazione diversa (non predefinita), RegistryComponent insisterà sul fatto che includo una dipendenza DatabaseComponent. E devo usare RegistryComponent, perché è ciò che richiede AlgorithmComponent di primo livello.

Mi manca qualcosa? Nel momento in cui utilizzo un self-type in uno qualsiasi dei miei componenti, dichiaro che tutte le implementazioni possibili devono utilizzare le stesse dipendenze.

Qualcun altro si è imbattuto in questo problema? Qual è il modo migliore di risolverlo?

Grazie!

+0

Come dice Dave, mancano interfacce, per disaccoppiare l'impls. Se stai cercando quello che manca al modello di torta, quindi guarda EJB e Spring: un contenitore che è a conoscenza di problemi quali transazioni, sicurezza e configurazione delle risorse. Il modello di torta non risponde a questo, e come tale, come Google Guice, è solo leggero. –

risposta

17

Con il modello di torta, almeno dallo example I always go to, è necessario separare la definizione dell'interfaccia del componente dall'implementazione predefinita. Questo separa in modo pulito le dipendenze dell'interfaccia dalle dipendenze dell'implementazione.

trait RegistryComponent { 
    // no dependencies 
    def registry: (List[Int] => List[Widget]) 
} 

trait DefaultRegistryComponent extends RegistryComponent { 
    this: DatabaseComponent => // default registry depends on the database 

    def registry: (List[Int] => List[Widget]) = new DefaultRegistry() 

    class DefaultRegistry extends (List[Int] => List[Widget]) { 
    def apply(xs: List[Int]) = xs.map(database(_)) 
    } 
} 
+5

Giusto. I self-types sono come qualsiasi altro tipo di dichiarazione di dipendenza. Possono mostrare dipendenza da un'entità concreta (cattiva) o astratta (buona). L'unico trucco è che il pattern di torta codifica sia le interfacce astratte che i componenti concreti come tratti, il che può essere fonte di confusione. –