MVVM et IOC: gestion des Invariants de classe du modèle View

il s'agit d'un problème avec lequel je me bats depuis que j'ai commencé à utiliser MVVM, d'abord à WPF et maintenant à Silverlight.

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

quoi ne devient un problème est de passer les données nécessaires au modèle de vue en utilisant IOC. Comme exemple simple, considérons un écran qui permet l'édition d'un client. En plus des services qu'il peut exiger, le modèle de vue pour cet écran nécessite un objet client pour afficher/éditer les données du client.

en faisant n'importe quel type de développement de bibliothèque (non-MVVM), je considère comme une règle unbendable que les invariants de classe soient passés par le constructeur. Dans les cas où j'ai besoin d' données propres au contexte pour le temps de construction de la classe et la classe en question est gérée par conteneur, j'ai tendance à utiliser une usine abstraite* comme un pont. Dans MVVM, cela semble exagéré, car la plupart des modèles de vue auront alors besoin de leur propre usine.

quelques autres approches que j'ai essayées / considérées incluaient (1) une méthode initialize/load dans laquelle je passe les données, ce qui viole la règle de forcer les invariants de classe à travers le constructeur, (2) passer des données par le conteneur comme paramètre overrides (Unity), et (3) passer des données à travers un sac d'état global (ugh).

quelles sont les solutions de rechange pour transmettre des données contextuelles d'un modèle de vue à l'autre? L'un ou l'autre des cadres de la MVVM traite-t-il de ce problème précis?

* qui peut avoir ses propres problèmes, comme exiger un choix entre un appel à conteneur.Resolve () ou ne pas avoir votre container-managed de ViewModel. Le château de Windsor dispose d'un bonne solution à cela, mais AFAIK aucun autre cadre ne le fait.

Edit:

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

29
demandé sur Phil Sandler 2011-06-07 19:09:59

3 réponses

j'avais l'habitude de lutter sur cette question beaucoup. Pour autant que je sache, il n'y a pas d'autres approches viables; vous semblez avoir déjà réfléchi en profondeur à la question par vous-même. Je veux juste deux ajouter mon deux 0,5 cents sur le raisons pourquoi je choisis assez souvent l'option (1):

  1. la méthode init est plus simple à mettre en œuvre que toute autre option (Eh bien, L'usine dactylographiée de Windsor est tout aussi facile);
  2. la conception de la faiblesse de ne pas avoir de constructeur paramètre peut être atténué l'exécution d'une vérification de l'initialisation des paramètres plus tard dans la VM du cycle de vie
  3. le "lieu" où vous appelleriez la méthode init est le même où vous auriez appelé le constructeur (ou l'usine abstraite);
  4. contrairement à abstract factory, vous pouvez prendre en compte la méthode init dans une interface spécifique afin de gérer plusieurs VM sur des chemins d'héritage différents (si neeeded);
  5. c'est un compromis juste (au moins dans ce contexte): si vous ne pouvez vraiment pas vivre avec, optez pour la solution d'usine sans vous soucier de la (très peu) complexité au-dessus.
5
répondu Marco Amendola 2011-06-07 15:59:51

Je ne suis pas tout à fait sûr de ce qu'est le problème, donc je vais utiliser un exemple simple et artificiel.

disons que vous avez un "151900920 qui contient un résumé de chaque client. Lorsque vous sélectionnez un client, vous souhaitez afficher un CustomerDetailViewModel . Cela peut prendre soit un ID client, soit un type ICustomer qui est rempli précédemment dans le CustomerListViewModel avec les détails du client (selon quand vous voulez charger les données par exemple).

I pensez ce que vous demandez est ce qui se passe si CustomerDetailViewModel prend également une série de services comme dépendances que vous voulez résoudre à travers le conteneur (normalement pour les chaînes de dépendances).

comme vous faites le modèle de vue en premier, vous devez instancier le CustomerDetailViewModel du CustomerListViewModel , et vous voulez le faire via le conteneur pour que les dépendances soient injectées de manière appropriée.

donc, comme vous le mentionnez, vous feriez normalement ceci par un dessin d'usine abstrait, par exemple ICustomerDetailViewModelFactory qui est passé comme un service au CustomerListViewModel .

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

lorsque vous résolvez le ICustomerDetailViewModel dans votre méthode d'usine GetCustomerDetailViewModel , vous pouvez spécifier la valeur que vous souhaitez utiliser pour le paramètre constructeur ICustomer lorsque vous appelez Resolve sur votre conteneur.

par exemple, Unity a parameter overrides , et voir ici pour Castle Windsor support. Castle Windsor a également un usine dactylographiée , de sorte que vous n'avez pas besoin de mettre en œuvre les types d'usine de béton, juste les abstractions.

donc j'opterais pour 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.

7
répondu devdigital 2017-05-23 12:34:02

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

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

à ce stade, mon point de vue n'a aucune connaissance du modèle de vue, et cela n'arrivera que lorsque j'injecterai le modèle de vue dans n'importe quel conteneur lié à la vue. J'utilise aussi Dataemplates pour rendre le ViewModel ce qui signifie que il n'est pas nécessaire d'instancier une vue directement pour fournir un DataContext.

donc, pour répondre, j'utiliserais (1), et je briserais la"règle".

2
répondu Mark Green 2011-06-07 15:39:38