I. Introduction▲
Beaucoup d'applications informatiques permettent une interaction avec l'utilisateur par l'intermédiaire de widgets affichant les données. Ces widgets (boutons, champs texte, etc.) répondent aux événements produits par une touche du clavier ou un clic de la souris, l'application effectuant alors une action.
Afin de séparer les responsabilités des différents composants de l'application, il est nécessaire de disposer d'un moyen de synchroniser les données avec leur affichage, et gérer les actions de l'utilisateur pour agir sur ces données.
Commençons par deux définitions :
- une vue est une représentation visuelle de données de l'application ;
- le modèle est un ensemble de composants contenant les données et les opérations logiques permettant la manipulation de ces données.
Tout au long de cet article, on se servira d'un exemple simple d'interface graphique pour illustrer nos propos.
Nous n'aborderons pas le modèle MVC dans le contexte d'une application Web, ce sujet nécessitant à lui seul un article complet.
I-A. L'application exemple▲
On se propose de développer la mini application suivante :
Lorsque l'utilisateur actionne le bouton, son score est calculé et affiché, sous forme textuelle (Label) et graphique (Slider). On ne peut pas faire beaucoup plus simple !
Le modèle est constitué par la valeur du score, les vues(1) de ce modèle sont le texte (Label) et la règle (Slider).
I-B. Le modèle Observateur▲
Il existe plusieurs stratégies « naïves » possibles pour synchroniser une vue avec le modèle.
La première consiste pour la vue à demander au modèle s'il a été modifié. La vue doit donc communiquer avec le modèle, ce dernier ignorant tout de la vue. Cette approche pose plusieurs problèmes :
- elle suppose une mise à jour régulière et à intervalles suffisamment courts pour ne pas afficher des données obsolètes ;
- si plusieurs vues affichent les mêmes données, elles peuvent être désynchronisées, affichant des données différentes si l'une des vues ne s'est pas mise à jour ;
- elle est coûteuse si les données changent peu souvent.
La seconde approche est l'inverse : le modèle modifie sa représentation visuelle. Cette approche n'est pas meilleure que la précédente :
- le modèle doit connaître ses représentations : il doit donc être modifié à chaque fois qu'on souhaite modifier sa représentation.
Aucune de ces approches n'est donc acceptable. On peut néanmoins trouver un compromis entre ces deux solutions. Le modèle sait quand son état est modifié : il peut donc le notifier. Les composants recevant cette notification pourront alors effectuer les mises à jour adéquates.
Le modèle de conception Observateur répond à cette problématique : il permet à un objet de notifier de ses changements d'état à une liste d'objets observateurs s'étant préalablement enregistrés pour recevoir cette notification. L'objet ainsi observé n'a pas besoin de connaître ses observateurs, et les observateurs sont prévenus d'une modification au moment adéquat.
Nous pouvons donc utiliser ce modèle de conception pour notifier des changements dans le modèle aux vues le représentant. Néanmoins, nous n'avons pas encore vu comment gérer les actions de l'utilisateur et agir sur le modèle.
I-C. Le concept de contrôleur▲
L'objectif du modèle de conception MVC (Model-View-Controller) est de séparer les objets du domaine (modèle) et les objets de présentation (vue). Il introduit le mécanisme de notification, via le modèle observateur, d'un objet du domaine rendant ainsi ces objets découplés de leurs représentations. En plus de ces objets, il introduit un nouvel objet, le contrôleur, chargé de recevoir et de gérer les actions de l'utilisateur, en communicant avec le modèle et la vue.
II. Le MVC classique▲
Le modèle MVC a été conçu originellement par l'équipe développant Smalltalk à la fin des années 70. Il permet de séparer par responsabilités le modèle logique du domaine, les représentations des données et la gestion des actions de l'utilisateur.
II-A. Les entités▲
La vue est un widget (2) sur l'écran : il peut s'agir d'un bouton, d'une étiquette, etc. dont le but est d'afficher les données du modèle. Elle communique directement avec le modèle.
Le contrôleur est une entité logique qui est toujours couplée à une vue. Il gère les entrées utilisateurs provenant des périphériques d'entrée tels que le clavier et la souris, et commande au modèle de manière appropriée. Il communique directement avec le modèle.
Le modèle est un ensemble d'objets métiers qui représentent le domaine du problème. Ces objets implémentent la logique métier. Ils ignorent tout de l'interface utilisateur, et donc ne connaissent rien de la vue ou du contrôleur.
II-B. Collaboration▲
Le diagramme montre la collaboration des objets M-V-C en se basant sur notre application. Lorsque l'utilisateur appuie sur le bouton « compute score », le contrôleur reçoit l'événement et ordonne au modèle une opération métier, à savoir calculer le score. Le modèle effectue l'opération, change son état et déclenche un événement notifiant de ce changement. Chaque vue (Label et Slider) répond à cet événement, demande le score au modèle et l'affiche.
Note : on voit que la vue peut demander au modèle les données qu'il contient (getScore), et que la vue et le contrôleur sont autorisés à communiquer directement (le contrôleur peut changer la vue : changeLabelColor par exemple).
II-C. Problèmes posés par ce modèle▲
Nous avons vu que le MVC classique fonctionne à merveille dans notre exemple simple. Le problème est que les interfaces réelles ont des cas d'utilisation beaucoup plus complexes.
En premier lieu, le contrôleur se voit attribuer des nombreuses responsabilités, y compris celle de modifier la vue. Pour réduire ces responsabilités, nous n'avons pas d'autre choix que de multiplier les contrôleurs pour que chacun ait un rôle restreint.
Par ailleurs, le modèle et la vue sont responsables de la logique de présentation. Pour illustrer cela, ajoutons une nouvelle règle au cas d'utilisation de notre application : si le score est supérieur à 100, la couleur du texte doit devenir verte. La question est donc : qui est responsable de cette logique de présentation ?
On peut demander au modèle d'effectuer la comparaison, celui-ci déclenchera alors un événement particulier, mais c'est peu commode et maladroit : à chaque nouvelle règle correspondra un nouvel événement ! On peut aussi étendre la classe Label affichant le score, qui changera alors de couleur si nécessaire, mais cela spécialise trop le widget associé (une autre règle impliquera une autre classe ?!).
C'est exactement pour ce genre de situation que le modèle d'application a été inventé.
III. L'Application Model MVC (AM-MVC)▲
Nous avons vu que le modèle MVC surcharge les composants en responsabilités. Afin de retirer à la vue et au modèle les responsabilités de logique de présentation, on peut ajouter une entité intermédiaire, nommée « modèle d'application ».
III-A. Les entités▲
La vue est un widget (3) sur l'écran : il peut s'agir d'un bouton, d'une étiquette, etc. dont le but est d'afficher les données du modèle. Elle communique uniquement avec le modèle d'application.
Le contrôleur est une entité logique qui est toujours couplée à une vue. Il gère les entrées utilisateurs provenant des périphériques d'entrée tels que le clavier et la souris, et commande au modèle d'application de manière appropriée. Il ne communique plus directement avec le modèle, mais avec le modèle d'application.
Le modèle est un ensemble d'objets métiers qui représente le domaine du problème. Ces objets implémentent la logique métier. Ils ignorent tout de l'interface utilisateur, et donc ne connaissent rien des autres composants.
Le modèle d'application agit en intermédiaire entre le modèle et les objets de présentation. Ainsi, le contrôleur n'accède plus directement au modèle, et la vue ne s'enregistre plus comme observateur auprès du modèle, mais ils communiquent avec le modèle d'application et s'enregistrent auprès de lui.
III-B. Collaboration▲
Lorsque l'utilisateur appuie sur le bouton « compute score », le contrôleur demande au modèle d'application de calculer le score. Celui-ci délègue la requête au modèle qui effectue le traitement associé et déclenche un événement « DataChanged ». Le modèle d'application l'intercepte, puis le déclenche de nouveau, et cette fois c'est la vue qui répond à l'événement. Elle demande alors au modèle d'application le score, ce dernier le demande au modèle, puis le fournit à la vue qui peut alors mettre à jour l'affichage.
Maintenant, le modèle d'application commence les traitements de la logique de présentation : il demande le score au modèle, et effectue la comparaison : si le score dépasse 100, il déclenche un événement « ColorChanged » auquel la vue répond en modifiant la couleur.
Le modèle d'application possède un autre avantage : il peut stocker des données relatives à la vue telles que la couleur du widget, l'élément sélectionné dans la liste, les widgets désactivés, etc.
III-C. Problèmes posés par ce modèle▲
Cependant, le modèle d'application n'est pas exempt de tout reproche. Le problème est qu'il n'est pas autorisé à manipuler la vue directement. Il ne peut donc pas changer la couleur du champ texte, mais est obligé de déclencher un événement de manière à ce que la vue y réponde. Une solution à ce problème est d'utiliser un autre modèle : le MVP.
IV. Le MVP▲
L'article original sur le MVP (Model-View-Presenter) a été publié en 1996 par Mike Potel, en tant que modèle de programmation pour les applications C++ et Java d'IBM. Il était basé sur le MVC classique de Smalltalk, et conçu pour correspondre aux systèmes modernes (4) , à savoir que les widgets reçoivent les événements clavier et souris. Ce modèle a été créé en détournant les modèles MVC et AM-MVC.
Plus tard, Martin Fowler décrit un modèle identique qu'il nomme « Supervising Controller », ainsi qu'un autre modèle proche de AM-MVC qu'il nomme « Passive View ». Nous allons étudier ces différentes approches.
IV-A. Entités▲
La vue est une composition de widgets. Elle répond aux actions de l'utilisateur et délègue le traitement au présentateur. Son rôle est d'afficher les données du modèle.
Le présentateur est chargé de la logique de présentation. Il contrôle le modèle, et change la présentation en fonction des règles de l'application. Il est étroitement lié à la vue.
Le modèle est proche de celui du MVC, mais libéré des problèmes de logique de présentation.
IV-A-1. Struture du Supervising Controller▲
IV-A-2. Structure du Passive View▲
IV-B. Collaboration▲
IV-B-1. Supervising Controller▲
Lorsque l'utilisateur clique sur le bouton, la vue délègue les entrées de l'utilisateur au présentateur. Ce dernier demande au modèle de calculer le score. Le modèle effectue l'opération, met à jour son état et déclenche un événement. La vue gère l'événement et demande au modèle les données pour les afficher. Le présentateur récupère le score, et en fonction de sa valeur, modifie la couleur de la vue.
IV-B-2. Passive View▲
En modifiant ce MVP sur le principe du AM-MVC, on obtient une seconde stratégie qui permet de découpler la vue du modèle.
Lorsque l'utilisateur clique sur le bouton, la vue délègue les entrées de l'utilisateur au présentateur. Ce dernier ordonne au modèle de calculer le score. Le modèle effectue l'opération, met à jour son état et déclenche un événement. Le présentateur gère l'événement, demande les données au modèle et modifie la vue, y compris sa couleur si nécessaire.
IV-C. Problèmes posés par ce modèle▲
On résout le problème d'applicabilité en donnant les responsabilités du contrôleur à la vue et au présentateur. La vue a la responsabilité de répondre aux événements (clavier, souris) et le présentateur celle de contrôler le modèle. Notons que dans le MVC, chaque widget est associé à un contrôleur, et chaque widget sur l'écran est une « vue ». Dans le MVP, il n'y a pas de contrôleur, on élimine donc la notion de vue-widget, et l'écran entier est alors la « vue »
Par ailleurs, le problème d'utilisabilité a été résolu en remplaçant le modèle d'application par l'entité de présentation qui peut communiquer directement avec la vue si nécessaire. Notons que dans le MVP Supervising Controller, le présentateur communique avec le modèle et s'enregistre pour recevoir des événements, alors que dans le AM-MVC la vue interagit avec le modèle seulement par le biais du modèle d'application. La patron Passive View de Fowler décrit un MVP qui ressemble plus au AM-MVC, car dans ce cas la vue n'interagit avec le modèle que via le présentateur.
On peut néanmoins reprocher à ce modèle d'être fortement lié à la vue : en effet, il communique directement avec elle, et y est donc fortement couplé. Le modèle AM-MVC n'introduit pas ce couplage, mais utilise une entité intermédiaire. Pour éviter ce couplage sans introduire une entité intermédiaire, on peut utiliser un autre modèle : le Presentation Model.
V. Le Presentation Model▲
Le modèle Presentation Model est une forme de MVP permettant de découplage du présentateur et de la vue.
V-A. Entités▲
La vue est une composition de widgets. Elle répond aux actions de l'utilisateur et délègue le traitement au modèle de présentation. Son rôle est d'afficher les données du modèle.
Le modèle est le même que celui de MVP.
Le modèle de présentation est chargé de la logique de présentation. Il contrôle le modèle, et change la présentation en fonction de son état. Il est découplé de la vue.
V-B. Collaboration▲
Lorsque l'utilisateur clique sur le bouton, la vue délègue les entrées de l'utilisateur au modèle de présentation. Ce dernier ordonne au modèle de calculer le score. Le modèle effectue l'opération, met à jour son état et déclenche un événement. Le modèle de présentation traite l'événement puis en déclenche un second à destination de la vue. La vue met alors à jour la valeur du score. Puis le modèle de présentation récupère le score, modifie son état interne (labelColor), et déclenche un événement ; la vue met alors à jour la couleur du score.
V-C. Problèmes posés par ce modèle▲
Le modèle PresentationModel résout le problème de couplage entre la vue et la logique de présentation. Il permet donc d'augmenter la testabilité de cette logique, étant donné qu'il n'est pas nécessaire de disposer des composants de vue.
Néanmoins, l'entité « modèle de présentation » conserve toujours les responsabilités de but utilisateur (contrôleur) et de présentation (présentateur). Il est donc impossible de les faire évoluer de manière autonome.
VI. Le MVPC▲
Lorsqu'on décide d'utiliser les modèles MVC et MVP, et même PresentationModel (qui n'est qu'une forme de MVP), on est forcé de coupler fortement la logique de but utilisateur et celle de présentation. En effet, les actions et les présentations en résultant sont indissociables.
Afin de les dissocier, on peut utiliser le modèle MVPC (Model-View-Presenter-Controller), qui inclut les deux entités de manière indépendante.
VI-A. Entités▲
La vue est une composition de widgets. Elle répond aux actions de l'utilisateur et délègue le traitement au présentateur. Son rôle est d'afficher les données du modèle.
Le modèle est le même que celui de Supervising Controller.
Le contrôleur est chargé par le présentateur de déléguer au modèle les opérations métier.
Le présentateur est le même que dans Passive View, sauf qu'il délègue au contrôleur la logique de but utilisateur (actions sur le modèle).
VI-B. Collaboration▲
Lorsque l'utilisateur clique sur le bouton, la vue délègue le traitement au présentateur, lequel le délègue à son tour au contrôleur. Ce dernier ordonne au modèle de calculer le score. Le modèle déclenche alors un événement, auquel le présentateur répond en modifiant l'affichage du score (valeur et couleur).
VI-C. Problèmes posés par ce modèle▲
Bien que ce modèle permette de disposer d'un couplage lâche entre logique de présentation et de but utilisateur et donc d'une forte cohésion, le présentateur est de nouveau couplé à la vue, ce problème pouvant être résolu en remplaçant cette entité par un modèle de présentation.
Même en appliquant ce modèle, le MVPC reste complexe à mettre en ?uvre, pour un gain pas nécessairement évident. Il est en effet rarement nécessaire en pratique de devoir séparer la logique de présentation et la logique de but utilisateur.
VII. Conclusion▲
Normalement arrivé ici, on est en mesure de comprendre le problème que pose l'utilisation du terme « contrôleur ». La principale cause de confusion et d'incompréhension de ce modèle est la mauvaise utilisation de ce mot. Le contrôleur du MVC a pour rôle de gérer les actions de l'utilisateur et de les traduire en commandes pour les objets métiers, et non de faire une médiation entre le modèle et la vue. Cette erreur d'interprétation fait qu'on utilise le terme « contrôleur » pour désigner le modèle d'application dans le AM-MVC et le présentateur dans le MVP, ce qui conduit à tout appeler « MVC ». Si on n'arrive pas à choisir, le patron MVPC sépare la logique de présentation en logique de vue (présentateur), et logique de but utilisateur (contrôleur), incluant ainsi les deux entités.
Pour résumer : le rôle du contrôleur est de gérer le comportement (intercepter les entrées utilisateur), et il peut être partagé par plusieurs vues. Il peut aussi choisir quelle vue afficher. Le présentateur a pour responsabilités de gérer le comportement lié à la vue (logique de présentation), et permet de mieux découpler le modèle et la vue.
Voici un tableau récapitulatif des modèles, résumant leur constitution, leurs avantages et inconvénients :
MVC | modèle, vue, contrôleur |
- simple - assigne des responsabilités aux composants |
- pas de composant responsable de la logique de présentation - multiplication des contrôleurs ou contrôleur ayant trop de responsabilités |
AM-MVC | modèle, vue, contrôleur, modèle d'application |
- assigne les responsabilités de présentation - conserve l'état de la vue |
- utilise des événements spécifiques pour modifier la vue |
MVP Supervising Controller | modèle, vue, présentateur |
- permet de modifier la vue sans événement spécifique - conserve l'état de la vue |
- couple fortement le présentateur à la vue |
MVP Passive View | modèle, vue, présentateur |
- permet de modifier la vue sans événement spécifique - conserve l'état de la vue - découple la vue du modèle |
- couple fortement le présentateur à la vue |
Presentation Model | modèle, vue, modèle de présentation |
- découple le présentateur et la vue - conserve l'état de la vue - utilise des événements génériques (PropertyChanged). |
- couplage des logiques de présentation et de cas d'utilisation |
MVPC | modèle, vue, contrôleur, présentateur | - découple la logique de cas d'utilisation et la logique de présentation |
- complexe - couplage présentateur-vue |
VIII. Bibliographie▲
Aviad Ezra, Twisting the MVC Triad - Model View Presenter (MVP) Design Pattern.
Derek Greer, Interactive Application Architecture Patterns.
Martin Fowler, Patterns of Enterprise Application Architecture.
Karsten Lentzsch, Desktop Patterns and Data Binding.
Martin Hunter, The MVPC Software Design Pattern.