2013-06-14 4 views
5

Sto scrivendo la mia prima applicazione WPF e vorrei chiederti aiuto per un problema che ho riscontrato.Sostituzione di ServiceLocator con DependencyInjection durante l'implementazione di finestre di dialogo modali nell'applicazione MVPM WPF

Sto provando a seguire lo schema MVVM e sono arrivato a un punto in cui ho bisogno di implementare finestre di dialogo modali. Ho cercato su Google/leggere l'argomento per un po 'di tempo e sono stato in grado di risolvere una soluzione. Tuttavia, durante il refactoring ho riscontrato un dilemma che riguarda l'utilizzo di un DI (constructor injection) come sostituzione di un localizzatore di servizi.

Ho intenzione di fare riferimento a questi: http://pastebin.com/S6xNjtWW.

mi piace molto l'approccio di Roboblob:

Primo: Egli crea un'astrazione di un finestra di dialogo modale (interfaccia). Ho chiamato l'IModalDialog interfaccia e questo è come sembra:

public interface IModalDialog 
{ 
    bool? DialogResult { get; set; } 
    object DataContext { get; set; } 

    void Show(); 
    bool? ShowDialog(); 
    void Close();   

    event EventHandler Closed; 
} 

Secondo: Un'astrazione del servizio finestra di dialogo modale:

public interface IModalDialogService 
{  
    void ShowDialog<TDialogViewModel>(IModalDialog view, TDialogViewModel viewModel, Action<TDialogViewModel> onDialogClose) where TDialogViewModel : class;   
    void ShowDialog<TDialogViewModel>(IModalDialog view, TDialogViewModel viewModel) where TDialogViewModel : class;   
} 

Terzo: L'attuazione concreta di IModalDialogService:

public class ModalDialogService : IModalDialogService 
    { 
    public void ShowDialog<TDialogViewModel>(IModalDialog view, TDialogViewModel viewModel, Action<TDialogViewModel> onDialogClose) where TDialogViewModel : class 
    { 
     // set datacontext 
     if (viewModel != null) 
     { 
      view.DataContext = viewModel; 
     } 
     ((System.Windows.Window)view).Owner = System.Windows.Application.Current.MainWindow; 
     // register 
     if (onDialogClose != null) 
     { 
      view.Closed += (sender, e) => onDialogClose(viewModel); 
     } 
     view.ShowDialog(); 
    } 


    public void ShowDialog<TDialogViewModel>(IModalDialog view, TDialogViewModel viewModel) where TDialogViewModel : class 
    { 
     this.ShowDialog(view, viewModel, null); 
    } 

quarto: Ci sono più implementazioni di IModalDialog. Ciascuna è una classe derivata da finestre che implementa IModalDialog.


Prima faccio la domanda (descrivere il problema), ho bisogno di spiegare questo in anticipo:

Diciamo che ho alcuni altri servizi, come ad esempio IMessageBoxService. Poi necessario dichiarare queste dipendenze nel costruttore MainWindowViewModel:

public MainWindowViewModel(IModalDialogService a,        
          IMessageBoxService b, 
          ...) 

così che io (sia a mano o con IOC contenitore come Unità, ecc) può iniettare.

Per poter utilizzare il servizio di finestra di dialogo modale c'è un pezzo mancante di puzzle: la capacità di risolvere un'implementazione concreta di IModalDialog in base a qualche chiave.

Roboblob nel suo articolo risolve questo ultimo pezzo del puzzle utilizzando un modello ServiceLocator:

public class Bootstrapper 
{ 
public static void InitializeIoc() 
    { 
    SimpleServiceLocator.SetServiceLocatorProvider(new UnityServiceLocator()); 
    SimpleServiceLocator.Instance.Register<IModalDialogService, ModalDialogService>(); 
    SimpleServiceLocator.Instance.Register<IMessageBoxService, MessageBoxService>(); 
    ...  
    SimpleServiceLocator.Instance.Register<IModalWindow, EditUserModalDialogView>(Constants.EditUserModalDialog); 
    } 

}

così ha dentro la sua MainWindowViewModel chiama semplicemente le classi statiche Get e risolve un'attuazione concreta di finestra IModalDialog basato su una chiave.

Anche Josh Smith usa un approccio simile nel suo articolo ma nei commenti dice che (DI-constructor injection) è un'opzione valida.

La risposta StackOverflow di riferimento descrive anche un WindowViewLoaderService simile che potrebbe essere modificato e utilizzato.


Quindi la domanda è - quale sarebbe il modo migliore per sostituire il ServiceLocator (che risolve le implementazioni concrete di IModalDialog) con un'iniezione di dipendenza?

Il mio treno di pensieri era:

  1. Una possibilità è (a causa del progetto non essendo molto grande/sviluppato da me solo) per creare semplicemente un nuovo servizio (ad esempio, chiamato IModalDialogResolver) che creerebbe e restituire nuove istanze di implementazioni concrete di IModalDialog. Avere tutti i servizi iniettati a mano.

  2. Poi ho pensato a un contenitore IOC (Unità). Non ho esperienza con questo. Ho pensato che forse non dovevo scrivere l'IModalDialogResolver perché potevo registrare le diverse implementazioni di IModalDialog con Unity container => ma come utilizzare il contenitore all'interno di MainWindowViewModel? Non riesco a passare il riferimento al costruttore in quanto sarebbe un passo indietro a ServiceLocation.
    Allora ho pensato che forse posso usare un contenitore unità nella bootstrapper per risolvere tutti i servizi e utilizzarne un altro interno all'interno del IModalDialogResolver. Ma non so se questa sia una buona idea riguardo l'uso raccomandato di Unity. So davvero troppo poco per giudicare questo. Ma qualcosa mi dice che questa non è una buona idea in quanto crea una dipendenza nascosta sul contenitore + se il contenitore è un singleton che sarebbe equivalente al solo passaggio del riferimento nel costruttore.

Per forse meglio spiegare il blocco mentale che ho: mi piacerebbe utilizzare un contenitore IOC (ad esempio l'Unità) per avere le interfacce costruiti e iniettati da essa. Ma allora non posso semplicemente mettere IModalDialog come parametro all'interno di un costruttore. Quindi probabilmente ho davvero bisogno di racchiudere questo all'interno di un servizio e di implementare me stesso - ma poi (ammesso che Unity possa farlo subito) non ha senso avere Unity lì, se non posso usarlo.

So che una delle alternative è quella di mettere questo servizio in una classe base, ma per ragioni, non considerarlo. Mi piacerebbe davvero conoscere il modo giusto per risolverlo usando l'iniezione di dipendenza.

+1

L'iniezione di dipendenza e la posizione del servizio non si escludono a vicenda: è possibile utilizzare l'iniezione di dipendenza per iniettare il localizzatore di servizio. Vedi anche questo per due diversi approcci (servizio di interazione e richiesta di interazione) per l'utilizzo di finestre di messaggio/finestre di dialogo in MVVM: http://stackoverflow.com/questions/16877671/showing-a-message-box-from-the-viewmodel-is -a-violazione-di-mvvm-how-to-avoid – lightbricko

+0

@ lightbricko Prima di tutto, grazie per la risposta. :) Quindi, se comprendo correttamente il "Servizio di interazione", sarebbe meglio creare un'interfaccia per IModalDialogResolver e implementare un'implementazione in esso, correggere? Domanda: Supponiamo di usare Unity per l'iniezione di dipendenza boostrapper, pensi che potremmo usare Unity per implementare l'IModalDialogResolver o lo faresti da zero? – kajovajo

+2

@lightbricko Iniezione di dipendenza e posizione di servizio sono soluzioni opposte allo stesso problema. Tecnicamente, non si escludono a vicenda, ma nulla di buono viene dal mescolarli. http://www.infoq.com/articles/Successioning-Dependency-Injection –

risposta

4

È perfettamente valido e ci si aspetta che tu acceda al contenitore IoC all'interno del tuo composition root.

In realtà questo dovrebbe essere l'unica posizione in cui si accede si contenitore.

Nell'esempio che hai fornito, è tutto ciò che sta accadendo: le implementazioni concrete vengono registrate nel contenitore all'interno della radice di composizione.

Quindi, per rispondere alla tua domanda, non è necessario sostituire l'uso del modello di localizzazione del servizio qui, poiché è solo un meccanismo per registrare i tipi nella radice della composizione, che è perfettamente valido.

Se si desidera creare un'istanza del servizio finestra di dialogo modale in base ad alcune condizioni di runtime, allora si dovrebbe iniettare una fabbrica di dialogo modello di servizio, invece (sempre un'astrazione con un'implementazione registrata nel vostro contenitore), e quindi la fabbrica avrebbe un metodo per creare il servizio di dialogo modello e questo metodo di produzione richiederebbe i parametri di esecuzione richiesti.

La vostra fabbrica potrebbe quindi rinnovare il servizio di dialogo modello appropriato in base ai parametri del tempo di esecuzione.In alternativa, potrebbe anche risolvere il servizio di dialogo modello appropriato dal contenitore, che ovviamente richiederebbe alla fabbrica di avere un riferimento al contenitore.

La maggior parte dei contenitori supporta i tipi di fabbrica automatizzati, pertanto è sufficiente definire l'interfaccia per la fabbrica e il contenitore implementerà automaticamente la fabbrica utilizzando le convenzioni. Castle.Windsor ad esempio ha lo Typed Factory Facility e Unity ha alcuni equivalenti.

+0

Grazie mille per la risposta. Il tuo post è stato molto utile, in particolare la parte relativa al tipo di fabbrica/fabbrica automatizzata. Penso che questo possa essere considerato un evento di risposta accettato a questo punto. Vorrei farti una domanda aggiuntiva sull'iniettare la fabbrica. Riconosco che scrivere un'interfaccia factory, quindi implementarla e farla iniettare nel costruttore è valida e perfettamente valida. Comunque mi hai incuriosito dal tipo di auto-factory in Unity. Ho seguito [questo] (http://stackoverflow.com/questions/3825270/unity-auto-factory-with-params) SO risposta e questi 2 articoli – kajovajo

+0

[link] (http://www.sharpfellows.com/post /Unity-IoC-Container-.aspx), [link] (http://ithinksharp.blogspot.cz/2010/02/unity-2-and-injectionfactory.html). Ho messo il mio tentativo qui: [pastebin] (http://pastebin.com/E2fkLuFh) Ho scritto le mie domande anche nel pastebin (spero che non sia un problema). Potresti darci un'occhiata e dirmi cosa ne pensi? Sarebbe molto apprezzato. – kajovajo

+0

Comincerei con una fabbrica manuale per cominciare, a meno che non ci sia una buona ragione (come una catena di dipendenze molto grande) per voler risolvere la finestra di dialogo modale tramite il contenitore. Quindi in tal caso, avresti appena creato la finestra di dialogo modale nella tua fabbrica, quindi registrerai la fabbrica nel contenitore e passerai l'astrazione della fabbrica al tuo codice che consuma. – devdigital