Xamarin.Forms

[Xamarin.Forms] MVVM - A quoi ça sert ?

par
publié le

Je m'en souviens encore, mon premier projet WPF où avec l'habitude des WinForms j'ai commencé à coder la logique métier dans le code-behind de la fenêtre. J'ai alors senti un regard lourd dans mon dos, un ricanement et, tel le Denis Brogniart du code, mon collègue qui me lâche son irrévocable sentence :

Avec WPF, tu dois faire du MVVM. D'ailleurs, à partir d'aujourd'hui, on va tous faire du MVVM.

Et donc, on s'y est tous mis. De façon purement dogmatique, sans rien y comprendre. Ni le pourquoi, ni le comment. Sans connaître ses véritables raisons d'être, sans en avoir étudié les avantages et contraintes. Sans même avoir conscience qu'il s'agissait d'une architecture logicielle et donc de la fondation de notre applicatif.

Alors donc, ce fût un grand moment de n'importe quoi où chacun cherchait par tous les moyens à contourner les problèmes posés par MVVM. Et quand tu commences à considérer ton architecture comme un problème, c'est qu'il y en a un sérieux dans l'équipe de développement. Bien entendu, les ressources sur le sujet étaient ténues à l'époque et consistaient principalement à la résolution des problèmes apportés par MVVM. Oui, on était tous dans le même bateau.

Les candidats de Koh Lanta sautent du bateau
Revenez, ce n'est pas si terrible MVVM !

Heureusement, une dizaine d'années plus tard tout cela a bien changé et... hein ? Quoi ? C'est toujours comme ça ? Des équipes qui font du MVVM sans réfléchir, sans le comprendre, juste parce qu'on leur a dit c'est comme ça.

Il est peut-être temps de prendre un peu de recul et de comprendre à quoi ça sert.


MVVM, mais ça vient d'où ?

Sans refaire un historique complet des technologies Microsoft, sachez qu'avec .NET 3.0 (sorti en 2007 il me semble) est apparu un nouveau paradigme de programmation, en particulier au niveau graphique. Exit les vieilles fenêtres grises figées sur lesquelles on n'avait aucune liberté visuelle et dans lesquelles on mélangeait allègrement l'interface utilisateur et le code métier. Bienvenue à la conception graphique via la technologie XAML. Le développeur s'est alors retrouvé devant deux langages distincts pour traiter l'affichage ou le code :

  • XAML : un langage déclaratif au gout de XML pour décrire l'interface graphique
  • Le code (C#, VB.Net...) : pour la logique, le code métier...

Voici un exemple de page Xamarin.Forms décrite en XAML : 

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples"
             x:Class="XamlSamples.MainPage">

    <StackLayout>
        <!-- Place new controls here -->
        <Label Text="Welcome to Xamarin Forms!"
               VerticalOptions="Center"
               HorizontalOptions="Center" />
    </StackLayout>
</ContentPage>

Bien entendu, il a fallu d'une façon ou d'une autre faire communiquer code et affichage. C'est pourquoi le concept de DataBinding est devenu central avec XAML : les différentes propriétés des contrôles visuels (texte, couleur, visibilité, switch...) sont liées à des propriétés dans le code et reflètent leurs valeurs et changement de valeurs de façon autonome et automatique.

Par exemple, une liste sera affichée en étant "bindée" à une collection dans le code. Une case à cocher sera "bindée" à une propriété booléenne...

Ici, même exemple que précédemment mais le texte du Label est "bindé" à la propriété RandomQuote d'une classe. On peut imaginer, par exemple, que RandomQuote est une citation célèbre extraite aléatoirement d'une liste.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples"
             x:Class="XamlSamples.MainPage">

    <StackLayout>
        <!-- Place new controls here -->
        <Label Text="{Binding RandomQuote}"
               VerticalOptions="Center"
               HorizontalOptions="Center" />
    </StackLayout>
</ContentPage>

Mais qui est chargé d'exposer la propriété RandomQuote ? Comment les changements de valeurs de cette propriété seront propagés jusqu'au XAML ? Et inversement si la valeur est modifiée par l'utilisateur au niveau de l'interface, comment l'information est remontée dans la classe d'origine ?

C'est pour répondre à ces questions et architecturer tout ceci que Microsoft a proposé un nouveau modèle (repris depuis un peu partout) : MVVM, c'est-à-dire Model-View-ViewModel.

Ce qu’il faut en retenir c’est que cette architecture a pour but de découpler les données (Model), la logique (ViewModel) et l’interface utilisateur (View). 

La View est décrite en XAML de façon purement déclarative. Elle dispose tout de même d'un code-behind dans lequel on peut coder en C#, mais cela doit se limiter à du code lié à l'affichage (déclencher une animation par exemple).

Le Model est la représentation d'une donnée, au sens métier du terme. 

Le ViewModel est la couche intermédiaire qui prépare les données et les expose à la View. C'est généralement là qu'on trouvera la logique de validation des données, l'exécution des calculs, l'appel aux différents services...

Il y a donc un découpage assez net et précis entre ce qui concerne l'affichage, ce qui va stocker les données et ce qui va préparer les données.

C'est là tout l'enjeu de MVVM, et pour que ça ne parte pas en cacahuètes, il y a bien entendu un certain nombre de règles à respecter.

Du bon usage de MVVM

MVVM, c'est comme un mogwai, ça a l'air tout mignon tant qu'on suit scrupuleusement les règles.

Guizmo
MVVM... tant qu'on respecte les règles !

Principes de base

  1. La View ne se préoccupe que de l'affichage et de l'interaction utilisateur. Elle ne contient ni ne manipule aucune donnée !
  2. Le ViewModel est agnostique de la View. Il ne sait ni quelle vue l'utilise ni comment elle compte le manipuler. Il sert à préparer les données à partir de la couche Model mais ne contient pas, en principe, de logique métier en lui-même.
  3. Le Model est agnostique du ViewModel et de la View. Un Model est parfaitement indépendant du reste du code, il est en mesure de vivre sa vie tout seul. Un Model est réutilisable directement dans un autre projet qui utiliserait la même couche métier, par exemple.
  4. Ne jamais, mais alors ne jamais le nourrir après minuit !

Ok, je vous parle depuis tout à l'heure de View, de Model et de ViewModel, mais tout cela reste bien abstrait, voyons donc ça plus en détails.

Le M, le V et le VM

V comme View 

La View est un élément visuel, une page par exemple, qui définit la mise-en-page et l'apparence, mais également l'interaction utilisateur (boite de dialogue...).

La View référence un ViewModel via sa propriété BindingContext. Les contrôles sur la View héritent du BindingContext de la View et sont liés à des propriétés et des commandes exposée par le ViewModel via le système de DataBinding.

Un DataBinding peut être personnalisé de diverses manières (validation, converters, changement de BindingContext...) mais nous y reviendrons en détail dans d'autres articles.

Toute la logique d'affichage qui ne peut pas être directement traitée en XAML est codée dans le code-behind de la page.

La View définit le comportement visuel et réagit aux changements d'états du ViewModel.

VM comme ViewModel

Le ViewModel est une classe non visuelle et qui ne référence en aucune façon ni la View ni aucun élément relatif à l'affichage. Il encapsule la logique de présentation mais pas son affichage. J'insiste car c'est une règle trop souvent bafouée et qui mène irrémédiablement au monstre spaghetti.

Un beau plat de spaghetti
MVVM... quand on commence à référencer la View dans le ViewModel

Le ViewModel expose des propriétés et des commandes auxquelles la View se lie par DataBinding : il notifie la View de ses changements d'états à travers l'interface INotifyPropertyChanged

En pratique, on crée souvent une classe de base qui implémente INotifyPropertyChanged et dont tous les ViewModels vont hériter. C'est plus simple comme ça.

Les listes d'objets sont généralement exposées sous forme d'ObservableCollections qui implémentent INotifyCollectionChanged pour informer la View de la suppression ou de l'ajout d'éléments dans la liste.

Le cas échéant, c'est le ViewModel qui contient la logique propre à l'application (mais pas la logique métier, en principe).

Le ViewModel se charge de préparer et de mettre en forme les données pour la View.

M comme Model

Le Model encapsule les données, la logique métier et la validation, même si c'est sujet à discussion. En pratique, la validation est parfois traitée directement par le ViewModel.

Les Models ne référencent jamais ni la View, ni le ViewModel. Ce sont des entités indépendantes.

Ils implémentent la plupart du temps INotifyPropertyChanged.

En fait, il y a deux écoles : 

  • Le ViewModel expose directement les Models et donc ceux-ci implémentent impérativement INotifyPropertyChanged
  • Le ViewModel encapsule les propriétés des Models et ce sont les propriétés du ViewModel qui sont bindée à la View. Dans ce cas, c'est le ViewModel qui implémente INotifyPropertyChanged

Qui a raison, qui a tort ? J'ai envie de dire, ça dépend du projet et du modèle de données. A titre personnel, je préfère encapsuler les propriétés dans le ViewModel, c'est bien plus propre : la View ne voit que ce dont elle a besoin.

R comme Résumé !

Pour résumer, le ViewModel prépare et expose des données pour que la View les affiche à l’écran. Mais le ViewModel ne sait pas ce qu’en fait la View, c’est à la View de se débrouiller avec ce que le ViewModel lui fournit.

Les Models sont indépendants à la fois de la View et du ViewModel. Ce sont eux qui contiennent la logique métier, bien que ce sujet soit ouvert à discussion (la doc Microsoft n'est pas toujours en accord avec elle-même à ce propos !).

Avantages de MVVM

Le but de MVVM est de découpler au maximum l'affichage de la logique et la logique des données. Mais pourquoi me direz-vous ?

  • Pour rendre la logique (ViewModels) et les données (Models) unitairement testables sans avoir d'interaction avec l'interface.
  • Pour rendre l'interface graphique testable indépendamment des données ! On peut, en effet, mocker les ViewModels et injecter des données de test directement dans la vue !
  • Pour éviter que les données encapsulent la logique métier / Pour éviter que la présentation des données encapsule la logique métier (rayez la mention inutile)
  • Pour élargir le Principe de Responsabilité Unique à l'affichage et à la présentation des données.
  • Pour éviter qu'une modification au niveau de l'affichage ait des répercussions sur le code (ceux qui ont codé en VB6 savent de quoi je parle...)
  • Pour rendre le code modulable et réutilisable, en particulier la couche métier.
  • Dans le monde des bisounours, on dit aussi que ça permet aux designers et aux développeurs de travailler séparément sans avoir à attendre que l'un ou l'autre ait terminé sa tâche. Dans le vrai monde, euh, vous avez un designer qui ne s'occupe que de dessiner les interfaces dans votre équipe ?
  • Pour rendre l'architecture du code lisible, on sait quoi va où
  • Pour rendre le code maintenable : il est découpé en briques élémentaires indépendantes. En modifier une n'aura pas de répercussions inattendues à l'autre bout du code
  • Pour éviter de coller des crottes de nez dans le code.

Pour résumer, MVVM c'est une place pour chaque chose et chaque chose à sa place.
L'idéal est que chaque Model n'ait aucune dépendance vis-à-vis des ViewModels et des Views et que chaque ViewModels n'ait aucune dépendance vis-à-vis des Views.

Les contraintes de MVVM

Alors, MVVM, c'est tout beau tout rose ?

Evidemment, raconté comme ça, ça a l'air d'être la panacée. En réalité, MVVM est une architecture logicielle et vient donc avec un certain nombre de règles et de contraintes.

C'est comme le code de la route. Il est là pour garantir la sécurité de tous et éviter les accidents, mais cela ne vient pas sans quelques restrictions à nos libertés (s'arrêter au rouge, ne pas rouler à contresens...).

La principale question qui se pose quand on se lance pour la première fois dans un projet architecturé autour de MVVM, c'est comment gérer les interactions utilisateurs dans le ViewModel.

Je vous donne un exemple, sans doute le premier auquel vous serez confronté : je clique sur un bouton SUPPRIMER, une boite de dialogue me demande de confirmer avant suppression. Simple n'est-ce pas ? Sauf que le code de suppression est dans le ViewModel, que la commande liée au bouton s'y exécute directement et donc, comment fait-on pour afficher une boite de dialogue quand on est dans un ViewModel qui ne doit rien savoir de l'affichage ?

Eh bien, ce n'est pas simple !

Et là, deux écoles s'affrontent : 

  • Ceux qui savent pourquoi ils utilisent MVVM et cherchent à continuer à en respecter les principes et bonnes pratiques. Cela demande plus de travail, cela demande de réellement réfléchir à l'architecture de l'application, mais ça assure une application bien structurée et maintenable dans le temps
  • Ceux qui ne se souviennent plus (ou n'ont jamais su) pourquoi ils utilisent MVVM et cherchent a résoudre le problème par toutes les plus mauvaises solutions qui soient. Celles qu'ils considèrent comme plus simples et rapides et qui au final transforment le code en un Gremlins qui au fil du temps est de plus en plus incontrôlable.
Le chef des Gremlins revisite massacre à la tronçonneuse
MVVM... quand tu ne respectes pas les règles !

Un exemple très simple, rencontré personnellement sur un projet :

Une page de l'application freezait de façon inexplicable. Je n'ai pas mis longtemps à lever le lièvre : la View était injectée dans le ViewModel !

Cette violation directe de MVVM a conduit à un effet extrêmement pervers : un deadlock. En fait, le ViewModel exécutait une méthode déclarée dans le code-behind de la View qui elle-même exécutait une méthode du ViewModel qui attendait un résultat de la View ! Et patatra ! Les pieds dans le tapis !

Cela a été corrigé en redécoupant proprement le code. Certes, cela a demandé plus de réflexion et mis plus de temps que le code départ qui était plus simple et plus rapide à mettre en place. Mais maintenant ça fonctionne et surtout ça continue de fonctionner malgré les évolutions de l'application ! Au final, prendre son temps fait gagner du temps.

En fait, le véritable défi avec MVVM est d'assurer la communication entre les différentes couches tout en prenant soin qu'elles ne se référencent pas les unes et les autres.

Conclusion

Il y a encore beaucoup à dire sur MVVM mais cet article est déjà bien trop long et je souhaitais rester à un certain niveau d'abstraction, n'aborder que les concepts de base.

J'espère que vous comprenez mieux les enjeux de MVVM, ses raisons d'être.

Si vous avez des questions ou si vous n'êtes pas d'accord (vous avez le droit !) laissez-moi un commentaire !

Je voudrais juste terminer en précisant que MVVM est un choix d'architecture, pas quelque chose qu'on vous impose et qu'il faut contourner !

Si MVVM ne vous convient pas, il existe des alternatives. Plutôt que massacrer MVVM, trouvez l'architecture qui convient à votre projet !

Commentaires

Pour utiliser les commentaires, merci d'accepter les cookies du groupe "Disqus".