1-Principes

Dans le cadre du développement d'une application graphique, le principe du paradigme Modèle/Vue/Contrôleur (=MVC) est de séparer les données, leur manipulation et leur visualisation dans des classes différentes. Pour caricaturer :
  • le modèle représente les données que l'application utilise. Il ne connaît ni la vue, ni le contrôleur.
  • la vue est la représentation graphique de ces données à l'écran. Elle "écoute" les modifications du modèle pour se modifier elle-même. Elle ne connaît donc que le modèle.
  • le contrôleur est la partie qui va lire/modifier le modèle en fonction des interactions de l'utilisateur avec la vue. Il n'a donc besoin de connaître que le modèle.
Ainsi, le modèle n'a pas à connaître la façon dont il est représenté à l'écran. On peut même le représenter facilement avec différente vue. Par exemple, un tableau à deux dimensions peut être visualisé sous forme de tables, camemberts, histogrammes, ... sans pour autant que sa structure soit adaptée à chaque cas.

L'objectif du paradigme MVC est de pouvoir développer les trois parties en parallèle. Pour cela, avant tout codage, il  est nécessaire de :
  • définir l'ensemble des méthodes permettant de lire/modifier les données du modèle,
  • définir l'ensemble des composants graphiques permettant d'afficher le modèle à l'écran et d'interagir avec l'utilisateur,
  • définir l'ensemble des méthodes qui permettent de modifier l'état de la vue.
A noter que le troisième ensemble peut être vide mais généralement, il contient des méthodes qui permettent d'invalider tel ou tel composant en fonction du traitement que l'on fait, de (de)sélectionner des cases, de masquer/afficher une fenêtre, ... Cet ensemble dépend donc fortement des besoins de l'application et de ce fait, est parfois difficile à définir.

2-En Java


Malheureusement, il est très difficile d'utiliser de manière stricte ce paradigme en Java. Par exemple, une vue peut utiliser plusieurs composants graphiques générant le même type d'évènements. En mode strict,  cela impose de créer autant de contrôleur que de composants, donc énormément de classes. Autre problème, si une partie de la vue doit être mise à jour en fonction de modifications dans le modèle (par exemple, une case à cocher qui devient invalide), c'est à la vue de le "détecter" puisque ni le modèle, ni le contrôleur n'ont accès à la vue. Cette "détection" est complexe à mettre en place puisqu'il faut que le modèle signale qu'une donnée a été modifiée et que la vue soit à l'écoute de ce signal.

En conclusion, il convient d'utiliser un paradigme moins strict en permettant au contrôleur d'accéder à la vue. Il pourra ainsi :
  • identifier la source d'évènements et donc gérer plusieurs composants émettant ce type d'évènement,
  • appeler directement des méthodes de la vue, afin de modifier celle-ci lorsque les traitements l'imposent.

Dans ces conditions, le nombre de classes reste raisonnable. Généralement, cela donne :
  • une ou plusieurs classes représentant la partie Modèle,
  • une classe de Vue par classe de Modèle plus une classe de Vue représentant la fenêtre principale,
  • une classe Contrôleur par Vue et par type d'évènement à capturer.
Remarques :
  • Dans le cas d'applications simples, on a tendance à n'utiliser qu'une seule classe Modèle pour toute l'application et donc une seule classe de Vue qui représente la fenêtre principale.
  • Une vue peut générer plusieurs type d'évènements. Dans ce cas, pour éviter d'obtenir un "mega-contrôleur" illisible, il est souhaitable de créer une classe Contrôleur par type d'évènement, voire plusieurs quand on veut séparer les traitements de différents types de composants. Par exemple, en Java, les boutons ainsi que les items de menu émettent des évènements ActionEvent. On peut donc parfois séparer le traitement de ces signaux dans deux classes Contrôleur.
  • Chaque interaction de l'utilisateur avec la vue ne nécessite pas forcément un traitement dans un contrôleur. Par exemple, il n'est pas forcément important de définir une méthode pour traiter le clic sur une case à cocher puisque le contrôleur a accès à la vue, il est sans doute plus intéressant de simplement lire son état lorsqu'on en a besoin.
  • Certaines interactions ont des conséquences uniquement sur le modèle, d'autres uniquement sur la vue et d'autres enfin sur les deux.

2.1°/ Implémentation

Pour créer une classe de Modèle, il faut :
  • définir comme attributs les données du modèle, généralement en protected au cas où l'on veuille étendre le Modèle par héritage,
  • définir des méthodes pour lire et modifier ces attributs,
  • éventuellement définir de nouvelles classes d'exceptions qui seront émises lorsque les modifications du modèle sont invalides.
  • définir les méthodes nécessaires au coeur de l'application
Pour créer une classe de Vue, il faut :
  • hériter de Stage ou Scene, le premier cas quand on définit la vue de la fenêtre principale,
  • définir un constructeur qui prend en paramètre un (ou plusieurs) modèle à visualiser dans la vue,
  • créer les composant de la vue dans le constructeur (ou une méthode annexe), notamment en utilisant les données du modèle,
  • créer une (ou plusieurs) méthode(s) qui permet d'associer un contrôleur aux composants graphiques dont les évènements sont gérés par celui-ci,
  • créer les méthodes de mise à jour de la vue.
Pour créer une classe de Contrôleur, il faut :
  • implémenter une (ou plusieurs) interface comme EventHandler<> ou ChangeListener<>,
  • définir un constructeur qui prend en paramètre un (ou plusieurs) modèle et qui crée la (ou les) vue pour ce(s) modèle(s).
  • définir les méthodes traitant les évènements en provenance de la vue et qui vont éventuellement modifier le modèle et la vue.
2.2°/ Problèmes et solutions classiques

Comme dit précédemment, une vue génère souvent différents type d'évènements. Dès lors, il existe deux solutions pour contrôler la vue :
  • soit le contrôleur implémente toutes les interfaces permettant de gérer ces évènements,
  • soit on crée plusieurs contrôleurs.
La première solution est simple à mettre en oeuvre mais peut conduire à un code du contrôleur volumineux et peu lisible. La deuxième solution pose un problème particulier : d'après la section précédente c'est le contrôleur qui est censé créer la vue. Comme il y a plusieurs contrôleurs, comment faire ? Deux solutions sont envisageables :
  • inverser l'ordre de création, à savoir c'est la vue qui crée les contrôleurs. C'est la solution la plus simple en terme de mise en oeuvre mais elle "brise" un peu plus le paradigme MVC puisque dans ce cas, la vue "connaît" les contrôleurs.
  • regrouper les contrôleurs dans une même classe, qui va créer la vue puis chaque contrôleur, en donnant une référence de la vue à ces derniers.

Bien souvent, une modification donnée de la vue est le résultat du traitement d'évènements différents. Quand ces évènements sont traités dans des contrôleurs différents, cela implique que ces derniers aient des morceaux de code redondants. Dans ce cas, il convient de créer une hiérarchie de classes de contrôleurs afin de mutualiser ces parties de code.

3°/ Exemple

On veut créer une application qui fait la somme ou la soustraction de deux nombres. Le total s'affiche immédiatement, et s'actualise dès qu'un des nombres est modifié. Le résultat visible est le suivant :
 
Capture décran 2022 04 01 à 15.12.12

La solution donnée ci-dessous se base sur une organisation MVC où il y a plusieurs contrôleurs qui ont accès à la vue. 

Pour créer cette interface, on a besoin :
  • d'une classe Model qui contient les données, à savoir les deux nombres. Le total étant calculé, il n'est pas nécessaire de le stocker.
  • d'une classe View qui représente la fenêtre principale. Cette classe n'a accès qu'à une instance de Model.
  • d'une classe ControlText qui implémente ChangeListener<String> afin de traiter chaque modification des champs de saisie.
  • d'une classe ControlButton qui implémente EventHandler<ActionEvent> afin de traiter le changement d'opération et l'activation du bouton "clear".
  • d'une classe Appli qui crée le modèle, puis la vue et enfin les deux contrôleurs.
Pour télécharger les fichiers, cliquez sur le liens suivants :
Remarques :
  • La classe Appli affiche la vue uniquement après la création des deux contrôleurs. En effet, il ne faut pas que l'application puisse générer des évènements avant que les contrôleurs soient reliés aux composants de l'interface.
  • Ce lien est créé par les contrôleurs eux-mêmes, dans leur constructeurs, grâce aux méthodes setButtonControler() et setTextControler() de la classe View. Ainsi, cette dernière n'a pas besoin d'accéder aux contrôleurs.