MVVM est un pansement pour les couches de liaison de données mal conçues. En particulier, il a été très utilisé dans le monde WPF / silverlight / WP7 en raison des limitations de la liaison de données dans WPF / XAML.
A partir de maintenant, je vais supposer que nous parlons de WPF / XAML car cela clarifiera les choses. Regardons 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éfini dans XAML. Ces objets C # sont généralement connectés à XAML via des propriétés DataContext sur des objets de présentation.
En conséquence, le graphe d'objets viewmodel doit être mappé sur le graphe d'objets 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 graphe d'objet viewmodel découple avec succès le graphe d'objet de modèle du graphe d'objet ui, mais au détriment d'une couche de modèle de vue supplémentaire qui doit être construite et gérée.
Si je veux déplacer des données de l'écran A à l'écran B, je dois jouer avec les modèles de vue. Dans l'esprit d'un homme d'affaires, il s'agit d'un changement d'interface utilisateur. Cela devrait se passer uniquement dans le monde de XAML. Malheureusement, cela arrive rarement. Pire encore, en fonction de la structure des modèles de vue et de la réactivité des modifications des données, un réacheminement des données peut être nécessaire pour réaliser ce changement.
Contournement de la liaison de données sans expression
Les liaisons WPF / XAML ne sont pas suffisamment expressives. En gros, vous devez fournir un moyen d'accéder à un objet, un chemin de propriété à traverser 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 avez besoin de lier une propriété en C # à quelque chose de plus complexe que cela, vous n’avez fondamentalement pas de chance. Je n'ai jamais vu une application WPF sans convertisseur de liaison transformant true / false en visible / collapsed. 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 instructions pour structurer votre code C #, qui peuvent être utilisées pour atténuer cette limitation. Vous pouvez exposer une propriété sur votre modèle de vue appelée SomeButtonVisibility et la lier simplement à la visibilité de ce bouton. Votre XAML est beau et joli maintenant ... mais vous vous êtes transformé en employé - vous devez maintenant exposer + mettre à jour les liaisons à deux endroits (l'interface utilisateur et le code en C #) lorsque votre interface utilisateur évolue. Si vous avez besoin du même bouton 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 regarder le XAML et voir quand le bouton sera visible. Dès que les reliures deviennent un peu triviales, je dois faire un travail de détective dans le code C #.
L'accès aux données est limité de manière agressive
É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 des données globales ou de session de manière cohérente dans l'ensemble de votre application.
L'idée de "l'utilisateur actuellement connecté" est un bon exemple. Il s'agit souvent d'un problème véritablement global au sein d'une instance de votre application. En WPF / XAML, il est très difficile d’assurer un accès global à l’utilisateur actuel de manière cohérente.
Ce que je voudrais faire, c'est utiliser le mot "CurrentUser" dans les liaisons de données pour désigner librement l'utilisateur actuellement connecté. Au lieu de cela, je dois m'assurer que chaque DataContext me donne un moyen d'obtenir l'objet utilisateur actuel. MVVM peut accepter cela, mais les modèles de vue vont être gâchis car ils doivent tous fournir un accès à ces données globales.
Un exemple où MVVM tombe
Disons que nous avons une liste d'utilisateurs. À côté de chaque utilisateur, nous souhaitons afficher un bouton "supprimer un 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 doivent pas connaître l’utilisateur actuellement connecté. Ils ne représentent que les enregistrements d’utilisateur de votre base de données, mais l’utilisateur actuellement connecté doit être exposé aux liaisons de données dans les lignes de votre liste. MVVM indique que nous devrions créer un objet viewmodel pour chaque ligne de la liste qui compose l'utilisateur actuellement connecté avec l'utilisateur représenté par cette ligne de la 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 ressemblera énormément à un objet utilisateur de la plupart des autres manières - il peut être nécessaire de refléter toutes les propriétés de l'objet de modèle utilisateur et de transmettre les mises à jour de ces données à mesure qu'elles changent. Cela semble vraiment difficile - encore une fois, MVVM fait de vous un employé en vous obligeant à maintenir cet objet semblable à l'utilisateur.
Considérez - vous devez probablement aussi 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 encore 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 à modifier chaque fois qu'une propriété serait ajoutée ou modifiée.
Pire encore, cette couche s'adapte à la complexité de votre interface utilisateur et non à 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 - ceci ne fait pas qu'ajouter un calque, il ajoute un calque avec beaucoup de surface supplémentaire!
Comment les choses auraient pu être
Dans le cas décrit ci-dessus, j'aimerais dire:
<Button Visibility="{CurrentUser.IsAdmin && CurrentUser.Id != Id}" ... />
CurrentUser serait exposé globalement à tous les XAML de mon application. Id ferait référence à une propriété sur le DataContext pour ma ligne de liste. La visibilité serait convertie automatiquement de 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 oblige ses utilisateurs à créer un désordre complet. Autant que je sache, certains blogueurs créatifs ont donné un nom à ce désordre et s'appelaient MVVM. Ne vous y trompez pas: il ne fait pas partie de la même classe que les modèles de conception GoF. C'est un bidon laid à contourner un système de liaison de données laide.
(Cette approche est parfois appelée "programmation réactive fonctionnelle" si vous souhaitez en savoir plus).
En conclusion
Si vous devez travailler en WPF / XAML, je ne recommande toujours pas MVVM.
Vous souhaitez que votre code soit structuré de la même manière que 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 coercions de valeur flexibles. C'est beaucoup mieux - plus lisible, plus accessible en écriture et plus facile à maintenir.
MVVM vous conseille de structurer votre code de manière plus détaillée et moins facile à gérer.
Au lieu de MVVM, créez des éléments pour vous aider à vous faire une bonne idée: Développez une convention pour exposer de manière cohérente l'état global à votre interface utilisateur. Construisez vous-même des outils à partir de convertisseurs de liaison, de MultiBinding, etc. qui vous permettent d’exprimer des expressions de liaison plus complexes. Construisez vous-même une bibliothèque de convertisseurs de liaison pour aider à rendre les cas de contrainte habituels 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 vont nuire à la qualité de votre produit final en vous poussant vers de la merde comme MVVM au lieu de vous concentrer sur votre domaine de problèmes.