Intro
Dans MVVM, la pratique habituelle consiste à demander aux vues de trouver leurs ViewModels en les résolvant à partir d'un conteneur d' injection de dépendances (DI). Cela se produit automatiquement lorsque le conteneur est invité à fournir (résoudre) une instance de la classe View. Le conteneur injecte le ViewModel dans la vue en appelant un constructeur de la vue qui accepte un paramètre ViewModel; ce schéma est appelé inversion de contrôle (IoC).
Avantages de DI
Le principal avantage ici est que le conteneur peut être configuré au moment de l'exécution avec des instructions sur la façon de résoudre les types que nous lui demandons. Cela permet une plus grande testabilité en lui demandant de résoudre les types (Views et ViewModels) que nous utilisons lorsque notre application s'exécute réellement, mais en l'instruisant différemment lors de l'exécution des tests unitaires pour l'application. Dans ce dernier cas, l'application n'aura même pas d'interface utilisateur (elle n'est pas en cours d'exécution; seuls les tests le sont), donc le conteneur résoudra les simulations à la place des types "normaux" utilisés lorsque l'application s'exécute.
Problèmes découlant de l'ID
Jusqu'à présent, nous avons vu que l'approche DI permet une testabilité facile pour l'application en ajoutant une couche d'abstraction sur la création de composants d'application. Il y a un problème avec cette approche: elle ne fonctionne pas bien avec les concepteurs visuels tels que Microsoft Expression Blend.
Le problème est que dans les exécutions d'applications normales et les exécutions de tests unitaires, quelqu'un doit configurer le conteneur avec des instructions sur les types à résoudre; De plus, quelqu'un doit demander au conteneur de résoudre les vues afin que les ViewModels puissent y être injectés.
Cependant, au moment de la conception, aucun de nos codes n'est en cours d'exécution . Le concepteur tente d'utiliser la réflexion pour créer des instances de nos vues, ce qui signifie que:
- Si le constructeur de vue nécessite une instance de ViewModel, le concepteur ne pourra pas du tout instancier la vue - il provoquera une erreur de manière contrôlée
- Si la vue a un constructeur sans paramètre, la vue sera instanciée, mais elle le
DataContext
sera null
ainsi nous obtiendrons une vue "vide" dans le concepteur - ce qui n'est pas très utile
Entrez ViewModelLocator
Le ViewModelLocator est une abstraction supplémentaire utilisée comme ceci:
- La vue elle-même instancie un ViewModelLocator dans le cadre de ses ressources et lie son DataContext à la propriété ViewModel du localisateur
- Le localisateur détecte en quelque sorte si nous sommes en mode conception
- S'il n'est pas en mode conception, le localisateur renvoie un ViewModel qu'il résout à partir du conteneur DI, comme expliqué ci-dessus
- Si en mode conception, le localisateur renvoie un ViewModel "factice" fixe en utilisant sa propre logique (rappelez-vous: il n'y a pas de conteneur au moment du design!); ce ViewModel est généralement pré-rempli avec des données factices
Bien sûr, cela signifie que la vue doit avoir un constructeur sans paramètre pour commencer (sinon le concepteur ne pourra pas l'instancier).
Résumé
ViewModelLocator est un idiome qui vous permet de conserver les avantages de la DI dans votre application MVVM tout en permettant à votre code de bien jouer avec les concepteurs visuels. Ceci est parfois appelé la «possibilité de fusion» de votre application (en référence à Expression Blend).
Après avoir digéré ce qui précède, voyez un exemple pratique ici .
Enfin, l'utilisation de modèles de données n'est pas une alternative à l'utilisation de ViewModelLocator, mais une alternative à l'utilisation de paires View / ViewModel explicites pour certaines parties de votre interface utilisateur. Vous constaterez souvent qu'il n'est pas nécessaire de définir une vue pour un ViewModel car vous pouvez utiliser un modèle de données à la place.