13

Non mi è chiaro come sia possibile progettare in modo da mantenere il riferimento al contenitore DI nella radice di composizione per un'applicazione Silverlight + MVVM.Mantenere l'utilizzo del contenitore di DI nella radice composizione in Silverlight e MVVM

Ho il seguente scenario di utilizzo semplice: c'è una vista principale (forse un elenco di elementi) e un'azione per aprire una vista di modifica per un singolo elemento. Pertanto, la vista principale deve creare e mostrare la vista di modifica quando l'utente esegue l'azione (ad esempio, fa clic su un pulsante).

Per questo ho il seguente codice:

public interface IView 
{ 
    IViewModel ViewModel {get; set;} 
} 

Poi, per ogni visualizzazione che ho bisogno di essere in grado di creare Ho una fabbrica di astratto, in questo modo

public interface ISomeViewFactory 
{ 
    IView CreateView(); 
} 

Questa fabbrica è poi dichiarato una dipendenza del modello di vista "padre", in questo modo:

public class SomeParentViewModel 
{ 
    public SomeParentViewModel(ISomeViewFactory viewFactory) 
    { 
     // store it 
    } 

    private void OnSomeUserAction() 
    { 
     IView view = viewFactory.CreateView(); 
     dialogService.ShowDialog(view); 
    }  
} 

quindi va tutto bene fin qui, nessuna dI -container in vista :). Ora arriva la realizzazione di ISomeViewFactory:

public class SomeViewFactory : ISomeViewFactory 
{ 
    public IView CreateView() 
    { 
     IView view = new SomeView(); 
     view.ViewModel = ???? 
    } 
} 

Il "????" parte è il mio problema, perché il modello di vista per la vista deve essere risolto dal contenitore DI in modo da ottenere le sue dipendenze iniettate. Quello che non so è come posso farlo senza avere una dipendenza dal DI-container ovunque tranne la radice della composizione.

Una possibile soluzione sarebbe quella di disporre di una dipendenza dal modello di vista che viene iniettato nella fabbrica, in questo modo:

public class SomeViewFactory : ISomeViewFactory 
{ 
    public SomeViewFactory(ISomeViewModel viewModel) 
    { 
     // store it 
    } 

    public IView CreateView() 
    { 
     IView view = new SomeView(); 
     view.ViewModel = viewModel; 
    } 
} 

Mentre questo funziona, ha il problema che, poiché il grafico intero oggetto è cablato "staticamente" (cioè il modello di visualizzazione "genitore" otterrà un'istanza di SomeViewFactory, che otterrà un'istanza di SomeViewModel, e questi vivranno fino a quando il modello di visualizzazione "genitore" vive), l'implementazione del modello di vista iniettato è stateful e se l'utente apre la vista figlio due volte, la seconda volta il modello di vista sarà la stessa istanza e avrà lo stato di prima. Immagino di poter aggirare questo problema con un metodo "Initialize" o qualcosa di simile, ma non ha un buon odore.

Un'altra soluzione potrebbe essere quella di avvolgere il DI-contenitore e hanno le fabbriche dipendono l'involucro, ma sarebbe ancora un DI-contenitore "in incognito" ci :)

ps: la mia soluzione attuale è che le fabbriche conoscono il DI-container, e sono solo loro e la radice di composizione ad avere questa dipendenza.

risposta

7

di rimanere il più vicino al tuo codice di esempio possibile, è possibile introdurre un ulteriore livello di indirezione, sotto forma di un IViewPopulator:

public interface IViewPopulator 
{ 
    void Populate(IView view); 
} 

È ora possibile implementare il SomeViewFactory in questo modo:

public class SomeViewFactory : ISomeViewFactory 
{ 
    private readonly IViewPopulator populator; 

    public SomeViewFactory(IViewPopulator populator) 
    { 
     if (populator == null) 
     { 
      throw new ArgumentNullException("populator"); 
     } 

     this.populator = populator; 
    } 

    public IView CreateView() 
    { 
     IView view = new SomeView(); 
     this.populator.Populate(view); 
     return view; 
    } 
} 

Questo separa la creazione di Views e la popolazione di ViewModels, attenendosi allo Single Responsibility Principle. In una certa misura, è anche un esempio di Service Aggregation.

È ora possibile implementare IViewPopulator come un tipo di cemento che prende le dipendenze normali:

public class SomeViewPopulator : IViewPopulator 
{ 
    private readonly IDependency dep; 

    public SomeViewPopulator(IDependency dep) 
    { 
     if (dep == null) 
     { 
      throw new ArgumentNullException("dep"); 
     } 

     this.dep = dep; 
    } 

    public void Populate(IView view) 
    { 
     var vm = // Perhaps use this.dep to create an instance of IViewModel... 
     view.ViewModel = vm; 
    } 
} 

Ci saranno probabilmente altri modi si potrebbe modellare il rapporto tra IView e IViewModel, ma la rappresenta una soluzione possibile in precedenza.

La chiave è continuare a estrarre le astrazioni fino a quando ciascuna ha una responsabilità ben definita. Questo esercizio non riguarda assolutamente la codifica del codice del contenitore, ma alla fine il rispetto dei principi SOLID.

+1

Grazie mille. Anche se la tua implementazione è cristallina, quello che ho fatto è stato semplicemente dichiarare le dipendenze per il modello di visualizzazione sulle mie fabbriche, in quanto sono davvero molto semplici e ho sentito che avere qualcosa come il tuo View Populator è eccessivo per me. Eppure, da un punto di vista SOLIDO e soprattutto se le cose si complicano, separare la responsabilità come hai fatto ha sicuramente molto senso. Grandi cose :) –

+0

Felice di essere di aiuto :) –

Problemi correlati