Model View View-Model a été développé par Microsoft pour cibler les plates-formes de développement d'interface utilisateur qui prennent en charge la programmation événementielle, en particulier Windows Presentation Foundation (WPF) et Silverlight sur les plates-formes .NET à l'aide des langages XAML et .NET. Depuis, de nombreux frameworks Javascript tels que Angular, Knockout et ExtJS ont adopté le modèle.
Comme la plupart des modèles de logiciels, MVVM a ses utilisations appropriées et ses abus. Dans quelles conditions l'utilisation de MVVM est-elle appropriée? Quand est-il mal avisé?
MVVM est destiné à être utilisé lorsque des interactions utilisateur complexes utilisant des interfaces utilisateur haute fidélité sont nécessaires (c'est-à-dire WPF ).
MVVM est destiné aux plates-formes de développement d'interface utilisateur modernes (Windows Presentation Foundation, ou WPF, et Silverlight) dans lesquelles il existe un développeur d'expérience utilisateur (UXi) qui a des exigences différentes de celles d'un développeur plus "traditionnel" (par exemple orienté vers la logique métier et développement back end). Le View-Model de MVVM est "fondamentalement un convertisseur de valeur sur les stéroïdes", ce qui signifie que le View-Model est responsable d'exposer les objets de données du modèle de telle manière que ces objets soient facilement gérés et consommés. À cet égard, le modèle de vue est plus un modèle que une vue et gère la plupart sinon la totalité de la logique d'affichage de la vue.
MVVM a été conçu pour utiliser des fonctions spécifiques dans WPF afin de faciliter la séparation du développement de la couche View du reste du modèle en supprimant pratiquement tout le "code-behind" de la couche View. Au lieu d'exiger des concepteurs interactifs qu'ils écrivent du code View, ils peuvent utiliser le langage de balisage WPF natif XAML et créer des liaisons vers le ViewModel, qui est écrit et maintenu par les développeurs d'applications. Cette séparation des rôles permet aux concepteurs interactifs de se concentrer sur les besoins UX plutôt que sur la programmation ou la logique métier, permettant aux couches d'une application d'être développées en plusieurs flux de travail.
Pour les interfaces utilisateur où ce type d'interaction riche n'est pas nécessaire, MVVM peut être excessif; MVP peut être un choix plus naturel. Pour les applications Web, MVC convient mieux. Pour les très petites applications qui ne grossiront jamais (comme les petits utilitaires Winforms), le code-behind est adéquat.
Parfois, MVVM peut être un piège. D'après mon expérience, il privilégie les applications de type CRUD (formulaires sur données) par rapport à des interfaces utilisateur plus orientées tâche. Je ne dis pas que cela implique une mauvaise architecture pour les couches dorsales/autres dans l'application, mais j'ai vu beaucoup d'applications MVVM avec une architecture "DDD light". Je ne sais pas pourquoi peut-être exactement parce que la liaison est si facile et il est très simple de configurer une application avec un ORM et MVVM/Binding en utilisant des objets de domaine POCO/Anemic.
MVVM est un pansement pour les couches de liaison de données mal conçues. En particulier, il a été beaucoup utilisé dans le monde WPF/silverlight/WP7 en raison des limitations de la liaison de données dans WPF/XAML.
À partir de maintenant, je vais supposer que nous parlons de WPF/XAML car cela clarifiera les choses. Voyons quelques-unes des lacunes que MVVM se propose de résoudre dans WPF/XAML.
Forme des données vs forme de l'interface utilisateur
La "VM" dans MVVM crée un ensemble d'objets définis en C # qui mappent sur un ensemble d'objets de présentation définis en XAML. Ces objets C # sont généralement connectés à XAML via les propriétés DataContext sur les objets de présentation.
Par conséquent, le graphique d'objet viewmodel doit être mappé sur le graphique d'objet de présentation de votre application. Cela ne veut pas dire que le mappage doit être un à un, mais si un contrôle de liste est contenu par un contrôle de fenêtre, il doit y avoir un moyen de passer de l'objet DataContext de la fenêtre à un objet qui décrit les données de cette liste.
Le graphique d'objet du modèle de vue dissocie avec succès le graphique d'objet de modèle du graphique d'objet ui, mais au détriment d'une couche de modèle de vue supplémentaire qui doit être créée et maintenue.
Si je veux déplacer des données de l'écran A vers l'écran B, je dois jouer avec les modèles d'affichage. Dans l'esprit d'un homme d'affaires, c'est un changement d'interface. Il devrait avoir lieu uniquement dans le monde de XAML. Malheureusement, c'est rarement possible. Pire encore, selon la façon dont les modèles de vue sont structurés et la façon dont les données changent activement, un certain réacheminement des données pourrait être nécessaire pour effectuer ce changement.
Contournement de la liaison de données inexpressives
Les liaisons WPF/XAML ne sont pas suffisamment expressives. Vous pouvez essentiellement fournir un moyen d'accéder à un objet, un chemin de propriété à parcourir et des convertisseurs de liaison pour adapter la valeur de la propriété de données à ce que l'objet de présentation requiert.
Si vous devez lier une propriété en C # à quelque chose de plus complexe que cela, vous n'avez pas de chance. Je n'ai jamais vu une application WPF sans un convertisseur de liaison qui s'est transformé en vrai/faux en visible/réduit. De nombreuses applications WPF ont également tendance à avoir quelque chose appelé NegatingVisibilityConverter ou similaire qui inverse la polarité. Cela devrait déclencher des sonneries d'alarme.
MVVM vous donne des directives pour structurer votre code C # qui peut être utilisé pour contourner cette limitation. Vous pouvez exposer une propriété sur votre modèle de vue appelée SomeButtonVisibility et simplement la lier à la visibilité de ce bouton. Votre XAML est agréable et joli maintenant ... mais vous vous êtes transformé en commis - maintenant vous devez exposer + mettre à jour les liaisons à deux endroits (l'interface utilisateur et le code en C #) lorsque votre interface utilisateur évolue. Si vous avez besoin que le même bouton soit sur un autre écran, vous devez exposer une propriété similaire sur un modèle de vue auquel cet écran peut accéder. Pire, je ne peux pas simplement regarder le XAML et voir quand le bouton sera visible. Dès que les liaisons deviennent légèrement non triviales, je dois faire un travail de détective dans le code C #.
L'accès aux données est très étend
Étant donné que les données entrent généralement dans l'interface utilisateur via les propriétés DataContext, il est difficile de représenter les données globales ou de session de manière cohérente dans toute votre application.
L'idée de "l'utilisateur actuellement connecté" en est un excellent exemple - il s'agit souvent d'une chose vraiment mondiale dans une instance de votre application. Dans WPF/XAML, il est très difficile d'assurer un accès global à l'utilisateur actuel de manière cohérente.
Ce que j'aimerais faire, c'est utiliser librement le mot "CurrentUser" dans les liaisons de données pour désigner l'utilisateur actuellement connecté. Au lieu de cela, je dois m'assurer que chaque DataContext me donne un moyen d'accéder à l'objet utilisateur actuel. MVVM peut s'adapter à cela, mais les modèles de vue vont être un gâchis car tous doivent donner accès à ces données globales.
n exemple où MVVM tombe
Disons que nous avons une liste d'utilisateurs. À côté de chaque utilisateur, nous souhaitons afficher un bouton "supprimer l'utilisateur", mais uniquement si l'utilisateur actuellement connecté est un administrateur. De plus, les utilisateurs ne sont pas autorisés à se supprimer.
Vos objets de modèle ne devraient pas connaître l'utilisateur actuellement connecté - ils ne représenteront que les enregistrements utilisateur dans votre base de données, mais en quelque sorte, l'utilisateur actuellement connecté doit être exposé aux liaisons de données dans vos lignes de liste. MVVM dicte que nous devons créer un objet viewmodel pour chaque ligne de liste qui compose l'utilisateur actuellement connecté avec l'utilisateur représenté par cette ligne de liste, puis exposer une propriété appelée "DeleteButtonVisibility" ou "CanDelete" sur cet objet viewmodel (en fonction de vos sentiments sur les convertisseurs de liaison).
Cet objet va ressembler énormément à un objet Utilisateur de la plupart des autres manières - il peut avoir besoin de refléter toutes les propriétés de l'objet modèle utilisateur et de transmettre les mises à jour à ces données à mesure qu'elles changent. Cela semble vraiment compliqué - encore une fois, MVVM fait de vous un commis en vous forçant à maintenir cet objet semblable à celui de l'utilisateur.
Considérez - vous devez probablement également représenter les propriétés de votre utilisateur dans une base de données, le modèle et la vue. Si vous avez une API entre vous et votre base de données, c'est pire - elles sont représentées dans la base de données, le serveur d'API, le client d'API, le modèle et la vue. J'hésiterais vraiment à adopter un modèle de conception qui ajouterait une autre couche qui doit être touchée chaque fois qu'une propriété est ajoutée ou modifiée.
Pire encore, cette couche évolue avec la complexité de votre interface utilisateur, pas avec la complexité de votre modèle de données. Souvent, les mêmes données sont représentées à de nombreux endroits et dans votre interface utilisateur - cela n'ajoute pas seulement une couche, il ajoute une couche avec beaucoup de surface supplémentaire!
Comment les choses auraient pu être
Dans le cas décrit ci-dessus, je voudrais dire:
<Button Visibility="{CurrentUser.IsAdmin && CurrentUser.Id != Id}" ... />
CurrentUser serait exposé globalement à tous les XAML de mon application. L'ID ferait référence à une propriété sur le DataContext pour ma ligne de liste. La visibilité passerait automatiquement du booléen. Toute mise à jour de Id, CurrentUser.IsAdmin, CurrentUser ou CurrentUser.Id déclencherait une mise à jour de la visibilité de ce bouton. Peasy facile.
Au lieu de cela, WPF/XAML force ses utilisateurs à créer un désordre complet. Pour autant que je sache, certains blogueurs créatifs ont donné un nom à ce gâchis et ce nom était MVVM. Ne vous laissez pas berner - ce n'est pas dans la même classe que les modèles de conception GoF. Il s'agit d'un hack laid pour contourner un système de liaison de données laid.
(Cette approche est parfois appelée "Programmation réactive fonctionnelle" au cas où vous souhaiteriez en savoir plus).
En conclusion
Si vous devez travailler en WPF/XAML, je ne recommande toujours pas MVVM.
Vous voulez que votre code soit structuré comme le montre l'exemple ci-dessus "comment les choses auraient pu être" - modèle exposé directement à la vue, avec des expressions de liaison de données complexes + des coercitions de valeurs flexibles. C'est beaucoup mieux - plus lisible, plus accessible en écriture et plus maintenable.
MVVM vous dit de structurer votre code de manière plus détaillée et moins maintenable.
Au lieu de MVVM, créez des trucs pour vous aider à approximer la bonne expérience: Développez une convention pour exposer l'état global à votre interface utilisateur de manière cohérente. Construisez-vous des outils à partir de convertisseurs de liaison, de MultiBinding, etc. qui vous permettent d'exprimer des expressions de liaison plus complexes. Construisez-vous une bibliothèque de convertisseurs de liaison pour aider à rendre les cas de coercition courants moins douloureux.
Encore mieux - remplacez XAML par quelque chose de plus expressif. XAML est un format XML très simple pour instancier des objets C # - il ne serait pas difficile de trouver une variante plus expressive.
Mon autre recommandation: n'utilisez pas de boîtes à outils qui forcent ce genre de compromis. Ils nuiront à la qualité de votre produit final en vous poussant vers des conneries comme MVVM au lieu de vous concentrer sur votre domaine problématique.
J'aime vraiment MVVM et je trouve ses défis motivants et je vois les nombreux avantages, mais ...
Pour les applications ou les jeux qui nécessitent beaucoup de code d'interface/d'interaction pour ajouter beaucoup de comportements personnalisés tout en gardant la perfection - il est souvent préférable d'utiliser un MVVM un peu sale - utilisez-le quand il est utile ou dans plus de données centrées par opposition à zones d'interaction centrées sur le code. Supposons que vous souhaitiez créer un contrôle et le déplacer entre différents contrôles parents ou le mettre en cache autrement ...
Il a une courbe d'apprentissage assez abrupte, donc à moins que vous n'ayez le temps, prévoyez de développer beaucoup en WPF/SL, ayez des concepteurs qualifiés dans Blend, ressentez le besoin d'écrire du code de test pour votre interface utilisateur ou attendez-vous à faire des années de maintenance pour votre projet - il pourrait ne pas porter ses fruits.
Peu de concepteurs connaissent Blend, tous les projets ne valent pas la peine de concentrer les tests automatisés sur la couche de présentation, car il doit être testé de toute façon manuellement et c'est ainsi que vous trouverez les bugs les plus importants, pas en testant vos machines virtuelles.
C'est vraiment un investissement. Vous devez d'abord comprendre les bases de WPF/SL et XAML, puis trouver les moyens de bien faire les liaisons, de raccorder vs à vms dans un certain ordre, de bien commander, de choisir un cadre dans la plupart des cas, ce qui pourrait être problématique en raison des licences, créer une bibliothèque de sippets pour coder efficacement, seulement pour constater que la plate-forme ne fonctionne pas toujours bien avec les liaisons et doit créer une bibliothèque de comportements qui vous fournit ce dont vous avez besoin.
Dans l'ensemble cependant - si vous surmontez tous les obstacles et devenez assez compétent dans le modèle - tout cela paie en termes de clarté, de maintenabilité et ... de vantardise? :)
Je suis un programmeur WPF/Silverlight depuis des années, développant d'énormes applications, telles que des systèmes de trading, sur MVVM.
Pour moi, au fil des années, j'ai appris que la MVVM stricte mange du temps et coûte de l'argent. Par strict, je veux dire des règles telles que "pas de code derrière".
Il est impossible dans autre chose que l'application de formulaire/base de données la plus basique de ne pas avoir de code.
Votre concepteur spécifiera le premier jour quelque chose qui n'est pas possible avec les contrôles standard, vous devez donc créer une série de contrôles personnalisés ou encapsuler des contrôles existants pour prendre en charge le paradigme d'interaction lorsque vous travaillez avec MVVM.
J'ai écrit toutes sortes de contrôles d'interface utilisateur swish en utilisant les mathématiques, l'inertie, les gestes, etc. et son travail acharné.
Mais tout cela est derrière du code, ce n'est tout simplement pas dans la vue. Vous devez gérer les clics sur les boutons, le défilement, le glissement, etc., mais tout est joliment enveloppé dans un ensemble de contrôles, ce qui rend ce code-ok correct.
Il est souvent plus facile de simplement écrire le code derrière et les choses intelligentes de l'interface utilisateur dans la vue, au lieu de créer un ensemble de contrôles juste pour le plaisir.
Le but de MVVM est de séparer la logique d'application/métier de la logique d'interface utilisateur. Le système de liaison et MVVM est une bonne façon de le faire.
Les autres objectifs vantés, comme le concepteur XAML sur un bureau, le programmeur C # sur l'autre, l'un travaillant dans Blend l'autre dans VS, est une erreur.
Être capable de repenser rapidement une interface utilisateur est une autre erreur. Cela n'arrive jamais, son optimisation prématurée; tout remaniement majeur nécessite beaucoup de travail pour les modèles de vue. L'ajustement est une chose mais des révisions rapides de l'interface utilisateur ne sont pas possibles; les modèles de vue doivent correspondre au paradigme d'interaction, de sorte que le couplage est plus serré que vous ne le pensez.
Pour moi, j'utilise MVVM pour garder ma logique d'application séparée (j'utilise généralement un contrôleur testable et un ensemble de modèles de vue sans logique) mais quel que soit le code et les astuces nécessaires pour que mon interface utilisateur ressemble et agisse de manière sexy, je ne le fais pas transpire.
Sinon, vous finissez par régner sur vos conceptions, vos animations sympas, etc. simplement parce que l'idée de comment gérer MVVM tout cela devient trop ahurissante.
MVVM, comme la plupart des choses dans la vie, a un sweet spot quelque part entre deux extrêmes.
La chose la plus importante dans la conception de logiciels est d'expédier votre produit tôt, de découvrir quelles fonctionnalités sont utilisées, quelle interface utilisateur fonctionne bien, et de ne pas écrire de beau code pour un produit que personne n'utilise.
Si votre application nécessite que vous vous liiez à des quantités excessives de données en temps réel, alors MVVM peut réellement gêner car il introduit des abstractions qui ralentissent le processus et, en supposant que nous parlons de WPF/Silverlight/WP7 en ce moment, la liaison le moteur n'est actuellement pas aussi efficace; bien que des améliorations soient en cours.
MVVM, en l'état, n'est pas non plus la seule partie de l'équation que vous devez considérer. Pure MVVM doit être pris en charge avec une infrastructure telle que le modèle Mediator pour vous permettre de communiquer entre des ViewModels déconnectés.
Malgré l'avis de blucz ci-dessus, MVVM est dans la lignée des modèles GoF. Si vous regardez les modèles, MVVM est l'équivalent du modèle Model View Passive Presenter, qui est apparu à peu près en même temps que MVVM. Ici, les gens se plaignent souvent de la complexité de MVVM simplement parce qu'ils ne comprennent pas comment concevoir son utilisation.
Un autre domaine dans lequel j'ai rencontré des problèmes avec MVVM est celui où vous devez intégrer une technologie tierce qui n'est pas conçue pour la prendre en charge (comme le cadre UII pour MS Dynamics). Parfois, vous devez vous demander si cela vaut la peine de "pirater" l'autre technologie uniquement pour travailler avec MVVM.
Si vous mélangez et faites correspondre quelque chose comme Win Forms dans votre solution WPF, cette partie de la solution ne conviendra probablement pas également à MVVM. J'espère que cela vous donne une idée de certains domaines où MVVM ne s'applique pas.
L'utilisation de MVVM n'a aucun sens lorsque:
C'est ça. Je ne peux pas vraiment comprendre pourquoi vous n'utilisez PAS MVVM lorsque vous travaillez avec WPF/Silverlight, sauf si vous testez ou déboguez quelque chose dans un projet distinct. Je trouve le modèle de conception idéal pour le développement WPF/Silverlight en raison de la façon dont les liaisons XAML fonctionnent.
L'intérêt de MVVM est que votre application entière est exécutée dans vos ViewModels, et vos vues sont simplement une jolie couche que les utilisateurs peuvent utiliser pour interagir avec vos ViewModels. Vous voulez cliquer sur un bouton, vous exécutez en fait une méthode ViewModel. Vous souhaitez modifier la page, vous modifiez en fait la propriété CurrentPage dans le ViewModel.
J'utilise MVVM pour tous les développements WPF/Silverlight, même les petits projets simples d'une seule page, bien que la façon dont j'utilise MVVM soit différente en fonction de la taille du projet et de mes besoins. La seule fois où j'ai fait une petite application sans MVVM, j'ai fini par le regretter (elle a ensuite été refactorisée pour utiliser MVVM lors de l'ajout d'une mise à jour)
J'ai travaillé pour un client sur un projet avec du code en arrière avec une tentative de conversion en MVVM lancé et c'était un gâchis géant. Cela ne veut pas dire que tous les projets code-behind sont un gâchis. Les vues pourraient provoquer des erreurs ou planter Blend, ce qui a causé des problèmes aux concepteurs.
J'ai essayé RoR et j'ai pratiquement omis de faire quoi que ce soit avec ASP.NET Webforms, mais lorsque ASP.NET MVC est sorti, j'ai essayé d'apprendre autant que possible, en utilisant souvent ASP.NET MVC In Action books et codecampserver comme référence. . Bien que ce soit un modèle différent, j'utilise bon nombre des choses que j'ai apprises des livres In Action dans le développement SL/MVVM.
Quand j'ai commencé à travailler avec Silverlight, j'ai été surpris de voir à quel point c'était nu et c'était comme une obligation de choisir l'un des nombreux cadres disponibles pour l'habiller. Je vois cela comme un problème avec les gens qui abandonnent l'apprentissage de MVVM.
J'ai commencé avec l'excellent MVVM-Light qui m'a aidé à comprendre le modèle. Plus tard, j'ai commencé à travailler avec Caliburn Micro, puis les lumières se sont allumées. Pour moi Caliburn Micro , c'est comme utiliser ASP.NET MVC sur des formulaires Web ASP.NET. Donc, même pour de petits exemples d'applications, je crée le projet, NuGet CM, et je suis en marche. Je suis la première approche de ViewModel et je garde mes vues muettes et faciles à utiliser dans Blend. Mes machines virtuelles peuvent être testées si vous êtes dedans et avec CM, il est assez facile de contourner des dispositions d'écran complexes.
Vous pouvez apprécier cette alternative. Cette option contourne la méthode WPF de liaison de données, de modèles de données et MVVM. Cette option ressemble davantage à l'ancienne approche WinForms designer.cs, simple et fiable.
Composites WPF
http://wpfcomposites.codeplex.com/
Ici, un code-code C # concis pour WPF est produit en superposant une matrice simple au-dessus de l'interface utilisateur via des composites basés sur la grille. Si une interface utilisateur XAML moderne ne contient que quelques étiquettes de texte et une liste de photos, pourquoi cela ne peut-il pas être défini par la simple ligne de code: grid.AddText (guid, coordonnée x, coordonnée y)? Remarque, ce n'est pas sur un canevas, mais toujours dans les conteneurs WPF: grille, dockpanel, etc. WPF est extrêmement puissant. Cette approche ne fait que tirer parti de cela.
Les développeurs ne se soucient généralement pas des matrices et des indices. Commencez avec un niveau de conteneur à grain grossier défini par le GUID des objets de transfert de données (DTO) ou POCO, puis complétez ces conteneurs à clé par une matrice à grain plus fin de lignes et de colonnes potentielles à l'intérieur?
Le projet codeplex ci-dessus introduit cette approche en commençant par le contrôle ListBox mais s'étend pour couvrir tous les contrôles composites WPF. Par exemple, chaque listboxitem est un composite ajouté avec un GUID (un composite lie un à un à une classe), puis à l'intérieur de cet élément, il y a une grille sur laquelle des éléments d'interface utilisateur enfants ( les enfants peuvent lier un à un aux propriétés d'une classe) peuvent être ajoutés à volonté via le code-behind. Les enfants peuvent être des blocs de texte ou des images.
L'idée est de tirer parti des indices et d'une structure d'élément d'interface utilisateur bien définie (cela force un ListBox sur le thème PresentationFramework.Aero comme base de départ) au lieu de modèles de données. Cette nouvelle approche limite délibérément ce qui peut être fait, mais ce faisant, elle génère un code-code C # concis et robuste qui est intuitif. Pas besoin de rechercher des solutions de modèles de contrôle pour styliser une barre de défilement ou d'encombrer une solution avec plusieurs modèles de données pour des tâches simples.