2013-07-08 13 views
10

Ho provato a usare il motivo a torta nel mio progetto e mi è piaciuto molto, ma c'è un problema che mi dà fastidio.Modello di torta alla Scala per oggetti con diverse durate

Il modello di torta è facile da usare quando tutti i componenti hanno la stessa durata. Basta definire più tratti-componenti, estenderli mediante tratti-implementazione e quindi combinare queste implementazioni all'interno di un oggetto, e tramite i tipi di autodiagnosi tutte le dipendenze vengono automaticamente risolte.

Ma supponiamo di avere un componente (con le sue dipendenze) che può essere creato come conseguenza dell'azione dell'utente. Questo componente non può essere creato all'avvio dell'applicazione perché non ci sono ancora dati per esso, ma dovrebbe avere una risoluzione di dipendenza automatica al momento della creazione. Un esempio di tale relazione di componenti è la finestra principale della GUI e i suoi sotto-argomenti complessi (ad esempio una scheda nel riquadro del blocco note) che vengono creati su richiesta dell'utente. La finestra principale viene creata all'avvio dell'applicazione e alcuni sottogruppi vengono creati quando l'utente esegue un'azione.

Questo è facilmente fatto in framework DI come Guice: se voglio più istanze di qualche classe, io inietto solo un Provider<MyClass>; quindi chiamo il metodo get() su quel provider e tutte le dipendenze di MyClass vengono risolte automaticamente. Se MyClass richiede alcuni dati calcolati dinamicamente, posso usare l'estensione inject assistita, ma il codice risultante si riduce a un provider/factory. Anche il concetto correlato, gli ambiti, aiuta.

Ma non riesco a pensare a un buon modo per farlo usando un motivo a torta. Attualmente sto usando qualcosa di simile:

trait ModelContainerComponent { // Globally scoped dependency 
    def model: Model 
} 

trait SubpaneViewComponent { // A part of dynamically created cake 
    ... 
} 

trait SubpaneControllerComponent { // Another part of dynamically created cake 
    ... 
} 

trait DefaultSubpaneViewComponent { // Implementation 
    self: SubpaneControllerComponent with ModelContainerComponent => 
    ... 
} 

trait DefaultSubpaneControllerComponent { // Implementation 
    self: SubpaneViewComponent with ModelContainerComponent => 
    ... 
} 

trait SubpaneProvider { // A component which aids in dynamic subpane creation 
    def newSubpane(): Subpane 
} 

object SubpaneProvider { 
    type Subpane = SubpaneControllerComponent with SubpaneViewComponent 
} 

trait DefaultSubpaneProvider { // Provider component implementation 
    self: ModelContainerComponent => 
    def newSubpane() = new DefaultSubpaneControllerComponent with DefaultSubpaneViewController with ModelContainerComponent { 
     val model = self.model // Pass global dependency to the dynamic cake 
    }.asInstanceOf[Subpane] 
} 

Poi impasto DefaultSubpaneProvider nella mia torta di livello superiore e iniettare SubpaneProvider in tutte le componenti che hanno bisogno di creare subpanes.

Il problema in questo approccio è che devo passare manualmente le dipendenze (model in ModelContainerComponent) dalla torta di livello superiore alla torta creata dinamicamente. Questo è solo un esempio banale, ma ci possono essere più dipendenze, e anche ci possono essere più tipi di torte create dinamicamente. Tutti richiedono il passaggio manuale delle dipendenze; inoltre, una semplice modifica dell'interfaccia di alcuni componenti può portare a una grande quantità di correzioni in più provider.

C'è un modo più semplice/più pulito per farlo? Come si risolve questo problema all'interno del modello di torta?

+0

Se la risposta non risolve la tua domanda, la cancellerò nuovamente. –

+0

E a proposito di qualcosa come – chemikadze

+0

'tratto ModelContainerComponentProxy estende ModelContainerComponent {def originalModelContainer: ModelContainerComponentProxy; def model = originalModelContainer.model} '- che potrebbe risolvere almeno il problema di passare esplicitamente il contenuto di tutti i componenti. – chemikadze

risposta

0

avete considerato le seguenti alternative:

  • Utilizzare classi interne in Scala, come hanno automaticamente l'accesso alle loro variabili membro della classe genitore.

  • ristrutturazione l'applicazione in un attore base di uno, perché sarà immediatamente beneficiare di:

    • Gerarchia/supervisione
    • Ascolto per la creazione/morte di componenti
    • corretta sincronizzazione quando si tratta di accesso stato mutevole

probabilmente sarà utile avere più codice per fornire una soluzione migliore, puoi condividere un sottoinsieme di compilazione del tuo codice?

+0

È impossibile utilizzare le classi interne per l'iniezione di dipendenza perché sono staticamente legate alle loro classi di inclusione (anche io non capisco come pensi che le classi interiori e le forme di torta siano correlate a questo proposito), e gli attori non possono essere utilizzato con codice GUI - Personalmente non conosco alcuna libreria GUI basata su attori. Sono anche abbastanza sicuro che il codice che ho fornito descriva già il problema, e non posso fornirne di più ora - Ho lavorato a quel progetto un anno e mezzo fa :) –

+0

Discutiamolo ulteriormente offline, sei disponibile su google chat? edmondo. porcu at gmail.com – Edmondo1984

+0

Ho paura che la domanda sia obsoleta ora. Sto lavorando ad altri progetti e non ho più bisogno di una risposta :) –

0

Diciamo che abbiamo un programma che ha solo due componenti: uno contiene la logica di business del nostro programma e l'altro contiene la dipendenza di questo programma, vale a dire la funzionalità di stampa.

abbiamo:

trait FooBarInterface { 
    def printFoo: Unit 
    def printBar: Unit 
} 

trait PrinterInterface { 
    //def color: RGB 
    def print(s: String): Unit 
} 

Per iniettare la logica fooBar, la torta-modello definisce:

trait FooBarComponent { 
    //The components being used in this component: 
    self: PrinterComponent => 

    //Ways for other components accessing this dependency.  
    def fooBarComp: FooBarInterface 

    //The implementation of FooBarInterface 
    class FooBarImpl extends FooBarInterface { 
     def printFoo = printComp.print("fOo") 
     def printBar = printComp.print("BaR") 
    } 
} 

noti che questa implementazione non lascia alcun campo non implementato e quando si tratta di mescolando questi componenti insieme, avremmo: val fooBarComp = new FooBarImpl. Per i casi in cui abbiamo solo un'implementazione, non dobbiamo lasciare fooBarComp non implementato. possiamo invece:

Non tutti i componenti sono così. Ad esempio Printer, la dipendenza utilizzata per la stampa di foo o bar deve essere configurata e si desidera poter stampare il testo con colori diversi. Quindi la dipendenza potrebbe essere necessaria per cambiare dinamicamente o impostare ad un certo punto nel programma.

trait PrintComponent { 

    def printComp: PrinterInterface 

    class PrinterImpl(val color: RGB) extends PrinterInterface { 
     def print(s:String) = ... 
    } 
} 

Per una configurazione statica, durante la miscelazione questa componente, potremmo per esempio hanno, diciamo:

val printComp = PrinterImpl(Blue)

Ora, i campi per l'accesso le dipendenze non devono essere valori semplici. Possono essere funzioni che prendono alcuni dei parametri del costruttore dell'implementazione delle dipendenze per restituirne un'istanza. Per esempio, potremmo avere Baz con l'interfaccia:

trait BazInterface { 
    def appendString: String 
    def printBar(s: String): Unit 
} 

e un componente della forma:

trait BazComponent { 
    //The components being used in this component: 
    self: PrinterComponent => 

    //Ways for other components accessing this dependency.  
    def bazComp(appendString: String) : Baz = new BazImpl(appendString) 

    //The implementation of BazInterface 
    class BazImpl(val appendString: String) extends BazInterface { 
     def printBaz = printComp.print("baZ" + appendString) 
    } 
} 

Ora, se avessimo la componente FooBarBaz, potremmo definire:

trait FooBarBazComponent { 
    //The components being used in this component: 
    self: BazComponent with FooBarComponent => 

    val baz = bazComp("***") 
    val fooBar = fooBarComp 

    //The implementation of BazInterface 
    class BazImpl(val appendString: String) extends BazInterface { 
     def PrintFooBarBaz = { 
      baz.printBaz() 
      fooBar.printFooBar() 
     } 
    } 
} 

Quindi abbiamo visto come un componente può essere configurato:

  • staticamente. (principalmente le dipendenze di livello molto basso)
  • dall'interno di un altro componente. (Di solito si tratta di uno strato di business configurare un altro livello di business, vedere "dipendenze che hanno bisogno di dati UTENTE " in here)

cosa differiva in questi due casi è semplicemente il luogo in cui la configurazione è in corso. Uno è per le dipendenze di basso livello al livello più alto del programma, l'altro è per un componente intermedio configurato all'interno di un altro componente. La domanda è, dove dovrebbe avvenire la configurazione per un servizio come Print? Le due opzioni che abbiamo esplorato finora sono fuori questione. Per come la vedo io, le uniche opzioni che abbiamo sono l'aggiunta di un Configuratore di componenti che si mescola in tutti i componenti da configurare e restituisce i componenti di dipendenza modificando le implementazioni.Ecco una semplice versione:

trait UICustomiserComponent { 
    this: PrintComponent => 

    private var printCompCache: PrintInterface = ??? 
    def printComp: PrintInterface = printCompCache 
} 

ovviamente possiamo avere più tali componenti configuratore e non c'è bisogno di avere una sola.

Problemi correlati