2010-05-10 15 views
10

J'utilise l'injection de dépendances de constructeurs dans mon application WPF et je continue de suivre le modèle suivant, donc j'aimerais avoir l'opinion des autres et entendre parler de solutions alternatives.Comment utiliser l'injection de dépendance de constructeur pour fournir des modèles d'une collection à leurs ViewModels?

L'objectif est de connecter une hiérarchie de ViewModels à une hiérarchie similaire de modèles, de sorte que la responsabilité de présenter les informations dans chaque modèle réside dans sa propre implémentation ViewModel. (Le motif apparaît également dans d'autres circonstances, mais MVVM devrait en faire un bon exemple.)

Voici un exemple simplifié. Étant donné que j'ai un modèle qui a une collection de modèles supplémentaires:

public interface IPerson 
{ 
    IEnumerable<IAddress> Addresses { get; } 
} 

public interface IAddress 
{ 
} 

Je voudrais refléter cette hiérarchie dans les ViewModels afin que je puisse lier un ListBox (ou autre) à une collection dans la personne ViewModel:

public interface IPersonViewModel 
{ 
    ObservableCollection<IAddressViewModel> Addresses { get; } 
    void Initialize(); 
} 

public interface IAddressViewModel 
{ 
} 

l'enfant ViewModel doit présenter les informations du modèle de l'enfant, il est donc injecté par le constructeur:

public class AddressViewModel : IAddressViewModel 
{ 
    private readonly IAddress _address; 

    public AddressViewModel(IAddress address) 
    { 
     _address = address; 
    } 
} 

la question est, quelle est la meilleure façon fournir le modèle enfant à l'enfant ViewModel correspondant?

L'exemple est trivial, mais dans un cas réel typique, les ViewModels ont plus de dépendances - chacune ayant ses propres dépendances (et ainsi de suite). J'utilise Unity 1.2 (bien que je pense que la question soit pertinente pour les autres conteneurs IoC), et j'utilise les stratégies de vue de Caliburn pour trouver automatiquement et connecter la View appropriée à un ViewModel.

Voici ma solution actuelle:

Le parent ViewModel a besoin de créer un enfant ViewModel pour chaque modèle de l'enfant, il a donc une méthode usine ajouté à son constructeur qu'il utilise lors de l'initialisation:

public class PersonViewModel : IPersonViewModel 
{ 
    private readonly Func<IAddress, IAddressViewModel> _addressViewModelFactory; 
    private readonly IPerson _person; 

    public PersonViewModel(IPerson person, 
          Func<IAddress, IAddressViewModel> addressViewModelFactory) 
    { 
     _addressViewModelFactory = addressViewModelFactory; 
     _person = person; 

     Addresses = new ObservableCollection<IAddressViewModel>(); 
    } 

    public ObservableCollection<IAddressViewModel> Addresses { get; private set; } 

    public void Initialize() 
    { 
     foreach (IAddress address in _person.Addresses) 
      Addresses.Add(_addressViewModelFactory(address)); 
    } 
} 

Une méthode d'usine qui satisfait l'interface Func<IAddress, IAddressViewModel> est enregistrée avec le UnityContainer principal. La méthode de fabrication utilise un conteneur enfant pour enregistrer la dépendance IAddress qui est requis par la ViewModel et résout alors le ViewModel de l'enfant:

public class Factory 
{ 
    private readonly IUnityContainer _container; 

    public Factory(IUnityContainer container) 
    { 
     _container = container; 
    } 

    public void RegisterStuff() 
    { 
     _container.RegisterInstance<Func<IAddress, IAddressViewModel>>(CreateAddressViewModel); 
    } 

    private IAddressViewModel CreateAddressViewModel(IAddress model) 
    { 
     IUnityContainer childContainer = _container.CreateChildContainer(); 

     childContainer.RegisterInstance(model); 

     return childContainer.Resolve<IAddressViewModel>(); 
    } 
} 

Maintenant, lorsque le PersonViewModel est initialisé, il boucle à travers chaque Address dans le modèle et les appels CreateAddressViewModel() (qui a été injecté via l'argument Func<IAddress, IAddressViewModel>). CreateAddressViewModel() crée un conteneur enfant temporaire et enregistre le modèle IAddress de sorte que lorsqu'il résout le IAddressViewModel à partir du conteneur enfant, le AddressViewModel obtient l'instance correcte injectée via son constructeur.

Cela semble être une bonne solution pour moi, car les dépendances des ViewModels sont très claires et elles sont facilement testables et ne connaissent pas le conteneur IoC. D'un autre côté, les performances sont correctes, mais pas excellentes, car beaucoup de conteneurs enfants temporaires peuvent être créés. Aussi, je me retrouve avec beaucoup de méthodes d'usine très similaires. Est-ce la meilleure façon d'injecter l'enfant? Modèles dans l'enfant ViewModels avec Unity?

  • Y a-t-il une meilleure façon (ou plus rapide) de le faire dans d'autres conteneurs IoC, par ex. Autofac?
  • Comment ce problème serait-il traité avec MEF, étant donné qu'il ne s'agit pas d'un conteneur IoC traditionnel mais qu'il est toujours utilisé pour composer des objets?

Répondre

2

En fonction du conteneur, ne pouvez-vous pas spécifier un paramètre (nommé ou non) dans la méthode CreateAddressViewModel de votre usine?

container.Resolve<IAddressViewModel>(new NamedParameterOverloads() { { "Address", model } }; 

Selon le récipient de votre usine peut avoir à connaître le nom du paramètre (TinyIoC et Castle afaik), ou il peut dû être le dernier dans la liste des dépendances du constructeur (YMMV en fonction des conteneurs), qui Ce n'est pas génial, mais cela évite de créer rapidement de nombreux conteneurs pour enfants, et le GC qui va les écraser va suivre, et vous obtenez toujours la DI pour toutes vos autres dépendances. Bien sûr, cela tombe si votre machine virtuelle a également une dépendance qui nécessite la même adresse I, dans ce cas, un conteneur enfant est probablement le chemin à parcourir à moins que vous ne vouliez que la VM connaisse le conteneur.

Mise à jour: Si vous utilisez un sous d'un conteneur qui utilise « victoires dernier registre » (que je pense que l'unité fait), alors vous pourriez passer le même conteneur enfant dans votre usine à chaque fois, et ont votre usine enregistre simplement la nouvelle IAddress - de cette façon vous ne créeriez pas une nouvelle instance de UnityContainer sur le tas pour chaque itération et elle devrait réduire les collectes de place si vous créez beaucoup d'éléments.

+0

Comme vous le faites remarquer, la spécification de paramètres ne fonctionne pas si l'une des dépendances nécessite le modèle, ce qui a été un coup de projecteur pour moi. Réutiliser le conteneur enfant est une possibilité, cependant. – GraemeF

0

L'exemple d'application ViewModel du WPF Application Framework (WAF) montre comment vous pouvez assembler le modèle et le ViewModel. L'exemple utilise MEF en tant que cadre d'injection de dépendances.

+0

En regardant à travers les échantillons que je n'ai trouvés nulle part qui fait cela - ils ont tendance à créer un seul ViewModel et à définir le modèle sur celui-ci lorsque la sélection change. Peut-être que je ne cherche pas au bon endroit, peux-tu me diriger vers la classe que tu avais en tête? – GraemeF