27 votes

MVVM et IOC : gérer les invariants de classe du modèle de vue

C'est un problème auquel je suis confronté depuis que j'ai commencé à utiliser MVVM, d'abord dans WPF et maintenant dans Silverlight.

J'utilise un conteneur IOC pour gérer la résolution des Views et ViewModels. Les Views ont tendance à être très basiques, avec un constructeur par défaut, mais les ViewModels ont tendance à accéder à de vrais services, qui sont tous nécessaires à leur construction. Encore une fois, j'utilise un conteneur IOC pour la résolution, donc l'injection de services n'est pas un problème.

Ce qui devient un problème, c'est de transmettre les données requises au ViewModel en utilisant l'IOC. À titre d'exemple simple, considérons un écran qui permet de modifier un client. En plus de tous les services dont il pourrait avoir besoin, le ViewModel de cet écran nécessite un objet client pour afficher/modifier les données du client.

Lors de tout type de développement de bibliothèque (non-MVVM), je considère comme une règle inflexible que les invariants de classe soient passés par le constructeur. Dans les cas où j'ai besoin de données spécifiques au contexte lors de la construction de la classe y la classe en question est gérée par un conteneur, j'ai tendance à utiliser une fabrique abstraite* comme pont. En MVVM, cela semble excessif, car la plupart des ViewModels auront besoin de leur propre fabrique.

Parmi les autres approches que j'ai essayées/envisagées, citons (1) une méthode d'initialisation/de chargement dans laquelle je passe les données, ce qui enfreint la règle consistant à forcer les invariants de classe par le biais du constructeur, (2) le passage des données par le conteneur en tant que paramètres superposés (Unity), et (3) le passage des données par un sac d'état global (ugh).

Quelles sont les autres façons de faire passer des données spécifiques au contexte d'un ViewModel à un autre ? L'un des cadres MVVM aborde-t-il ce problème spécifique ?

* ce qui peut avoir ses propres problèmes, comme le fait de devoir choisir entre un appel à Container.Resolve() ou de ne pas avoir votre ViewModel géré par conteneur. Castle Windsor a une bonne solution à ce problème, mais aucun autre framework ne le fait.

Edit :

J'ai oublié d'ajouter : certaines des options que j'ai énumérées ne sont même pas possibles si vous faites du MVVM "View First", à moins que vous ne passiez les données d'abord à la vue et ensuite au ViewModel.

5voto

devdigital Points 22495

Je ne suis pas tout à fait sûr de ce qu'est la question, donc je vais utiliser un simple exemple artificiel.

Disons que vous avez un CustomerListViewModel qui énumère un résumé de chaque client. Lorsque vous sélectionnez un client, vous souhaitez afficher un CustomerDetailViewModel. Cela pourrait prendre soit un ID de client, ou un ICustomer type qui est rempli précédemment dans l' CustomerListViewModel avec les informations du client (selon le moment où vous souhaitez charger les données par exemple).

Je pense que ce que vous demandez est ce qui se passe si CustomerDetailViewModel prend également une série de services comme des dépendances dont vous souhaitez régler par le conteneur (normalement pour la dépendance des chaînes).

Comme vous êtes en train de faire afficher au premier modèle, vous devez instancier l' CustomerDetailViewModel de la CustomerListViewModel, et vous souhaitez le faire via le récipient de sorte que les dépendances sont injectés de manière appropriée.

Donc, comme vous le mentionnez, vous le feriez normalement ce par le biais d'un résumé modèle de fabrique, par exemple, ICustomerDetailViewModelFactory qui est transmis comme un service à l' CustomerListViewModel.

Ce type d'usine a un ICustomerDetailViewModel GetCustomerDetailViewModel(ICustomer customer) méthode. Ce type d'usine nécessitera une référence à votre conteneur IoC.

Lors de la résolution de l' ICustomerDetailViewModel votre GetCustomerDetailViewModel méthode de fabrique, vous pouvez spécifier la valeur que vous souhaitez utiliser pour le ICustomer paramètre du constructeur lorsque vous appelez Résoudre sur votre récipient.

Par exemple, l'Unité a paramètre remplace, et voir ici pour le Château de Windsor soutien. Le château de Windsor dispose également d'une tapée de l'usine de facilité, de sorte que vous n'avez pas besoin de mettre en œuvre l'usine de béton, types, seulement des abstractions.

Donc, je voudrais aller avec l'option 2! Nous utilisons Caliburn.Micro, il résout beaucoup de MVVM problèmes, mais je ne sais pas du tout des cadres de répondre à cette question.

5voto

Marco Amendola Points 1124

J'ai souvent été confronté à cette question. Pour autant que je sache, il n'y a pas d'autres approches viables ; vous semblez avoir déjà profondément réfléchi à la question par vous-même. Je veux juste ajouter mon dos 0,5 centime sur le raisons C'est pourquoi je choisis assez souvent l'option (1) :

  1. la méthode init est plus simple à mettre en œuvre que toutes les autres options (enfin, la fabrique typée de Windsor est tout aussi simple) ;
  2. la faiblesse de conception de ne pas avoir un paramètre de contructeur pourrait être atténuée en imposant une vérification des paramètres d'initialisation plus tard dans le cycle de vie de la VM.
  3. l'"endroit" où vous appelleriez la méthode init est le même que celui où vous auriez appelé le constructeur (ou la fabrique abstraite) ;
  4. Contrairement à l'abstract factory, vous pouvez factoriser la méthode init dans une interface spécifique afin de gérer plusieurs VM sur différents chemins d'héritage (si nécessaire) ;
  5. c'est un compromis équitable (du moins dans ce contexte) : si vous ne pouvez vraiment pas vivre avec, optez simplement pour la solution d'usine sans vous soucier de la (très faible) complexité supplémentaire.

2voto

Mark Green Points 2037

Je ne suis pas sûr que MVVM et IoC se prêtent à l'utilisation d'invariants de classe dans les constructeurs. D'après mon expérience, les ViewModels sont créés à la suite d'une ICommand.Execute, ce qui permet un processus simple en deux étapes :

var vm = Container.Resolve<CustomerViewModel>();
vm.Model = CustomerRepository.GetCustomerModel(id);

À ce stade, ma vue n'a aucune connaissance du ViewModel, et cela ne se produira que lorsque j'injecterai le ViewModel dans le conteneur lié à la vue. J'utilise également des DataTemplates pour rendre le ViewModel, ce qui signifie que je n'ai pas besoin d'instancier directement une vue pour fournir un DataContext.

Donc, pour répondre, j'utiliserais (1), et j'enfreindrais la "règle".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X