Objets interdépendants et architecture Modèle Vue Contrôleur
Accès rapide:
la poule et l'oeuf,
les interfaces,
objet métier et IHM,
architecture MVC,
synthèse.
Dès qu'un projet Java prend un peu d'importance,
le nombre d'objets définis croit rapidement et la gestion de leurs interdépendances
devient critique.
La poule et l'oeuf
Il est impératif d'éviter toute circularité dans l'utilisation réciproque des objets.
Certains langages (dont Java) permettent de définir des objets interdépendants en procédant par étapes:
- création d'une classe A, avec quelques propriétés et méthodes
- création d'une classe B utilisant A
- modification de A avec utilisation de propriétés ou méthodes de B
- enrichissement itératif des interdépendances
Ce mode de fonctionnement obscurcit les développements et rend la maintenance difficile.
Il devient impossible de recréer un objet en l'absence d'une version antérieure de l'autre.
Les interfaces
Il est souvent nécessaire de créer des objets fortement interdépendants. Des objets distincts sont créés à la place d'un objet unique si leurs périmètres respectifs sont clairement identifiables. La nature de leur interaction doit être clairement identifiée et matérialisée comme point de soudure.
Les interfaces spécifient et matérialisent les points de soudure entre objets.
Chaque objet présente
- des propriétés
- des méthodes
- éventuellement des interfaces qui décrivent un ensemble cohérent de services
Une interface désigne un ensemble de services
- certains objets utilisent ces services
- certains objets implémentent ces services
- l'objet utilisateur n'a rien à connaître de l'objet réel fournissant le service, hormis une référence sur l'interface
- l'interface est abstraite et peut être spécifiée avant toute implémentation et utilisée par tout objet sans aucun pré requis
Objet métier et interface graphique de gestion
Un objet métier spécifie et implémente les propriétés d'un objet réel, comme un client ou une commande.
Un objet métier doit rester aussi simple que possible, ses méthodes se bornant à gérer
son état et sa cohérence.
Un objet métier ne doit pas gérer lui-même les dépendances complexes entre objets élémentaires.
Ce rôle doit être délégué à un modèle d'objets.
Un objet graphique évolué, comme une fenêtre, est souvent dédié à l'affichage
ou à la gestion d'un objet métier.
Une fenêtre a besoin d'accéder au détail de l'objets qu'elle affiche,
et donc de le connaître précisément.
Réciproquement, un objet n'a pas à connaître la structure et le fonctionnement d'une fenêtre
qui l'affiche ou qui le gère, mais si l'objet est modifié la fenêtre doit en être informée
pour modifier son affichage.
L'objet métier émet un message de modification que la fenêtre reçoit et traite.
Pour cela, la fenêtre s'est enregistrée auprès de l'objet métier non pas en tant que fenêtre
mais en tant que simple interface capable de recevoir un message de modification.
En résumé
- un objet métier est simple et autonome
- l'interface graphique connaît le détail de l'objet qu'elle affiche
- l'objet métier est capable d'envoyer un message aux objets intéressés
par sa modification
- la fenêtre reçoit les messages indiquant qu'un objet métier est modifié
et adapte l'affichage
- une interface de traitement de message est implémentée par la fenêtre
et s'enregistre auprès de l'objet métier
Ce schéma fonctionne correctement dans le cas ou plusieurs fenêtre affichent
simultanément un même objet métier.
Une méthode simple pour gérer ce cas de figure est l'utilisation de la norme de conception
(design pattern) émetteur / auditeur pour un événement "propertyChange":
Lors de chaque modification un événement de modification de propriété "PropertyChangeEvent" est créé par l'objet métier, décrivant
- la source de la modification (l'objet métier "
client
" lui-même)
- la propriété concernée (le nom du client "
NomClient
")
- l'ancienne valeur ("
Dupond
")
- la nouvelle valeur ("
Dupont
")
La fenêtre s'est préalablement enregistrée auprès de l'objet métier en tant qu'auditeur
- la fenêtre implémente la méthode unique
propertyChange(PropertyChangeEvent)
de l'interface
PropertyChangeListener
(auditeur de changement de propriété)
- l'objet métier "
Client
" gère une liste
de PropertyChangeListener
s préalablement enregistrés par la méthode
addPropertyChangeListener(listener)
.
Les objets représentés par cette interface peuvent être des fenêtres
ou n'importe quoi d'autre, mais seront informés des modifications
- à chaque modification, l'objet métier appelle la méthode
propertyChange
de chacun des PropertyChangeListener
s enregistrés.
Cette approche est totalement compatible avec la norme Java Beans, fonctionne dans tous les cas et permet à la fenêtre de savoir précisément quoi mettre à jour. Elle peut être remplacée par quelque chose de plus simple, comme l'envoi lors de chaque modification globale de l'objet métier d'un événement simple (EventObject) spécifiant seulement la source de cette modification. L'important est que l'interface graphique soit informée et adapte son affichage.
Architecture Modèle Composant Vue
L'architecture MVC systématise et complète le schéma objet / visualisation
- Le modèle décrit la structure et les propriétés des données manipulées
- La vue affiche les données et capture les actions sur l'interface graphiques
- Le contrôleur interprète les messages issus de la vue et met à jour le modèle
Le modèle élargit la notion d'objet par une approche globale comme une liste,
une structure arborescente ou tabulaire, ou des notions plus complexes comme un document.
Le modèle ne contient que des données sous une forme aussi brute et simple que possible,
et ne doit rien connaître de l'affichage
La vue doit afficher le modèle et intercepter les événements utilisateurs,
mais en aucun cas mettre à jour directement le modèle.
Elle délègue le traitement des événements utilisateurs au contrôleur
Le contrôleur gère les actions utilisateur transmises par la vue et met à jour le modèle en conséquence. La mise à jour du modèle informe la vue qui adapte l'affichage mais ne doit pas envoyer de message vers le contrôleur.
Les dépendances entre objets découlent de leurs attributions respectives
- le modèle contient les données et doit être connu en détail de la vue et du contrôleur
- le modèle informe la vue de ses modifications internes par une interface banalisée, et n'a pas à connaître le contrôleur
- la vue gère l'affichage du modèle et informe le contrôleur des actions utilisateur. Elle ne doit pas avoir à connaître les détails du contrôleur ni dépendre directement de lui
- le contrôleur reçoit les actions utilisateur de la vue. Un contrôleur est dédié à une vue et peut avoir à la connaître en détail. Il intervient directement sur le modèle qu'il met à jour.
- l'interface entre la vue et le contrôleur peut se limiter à des envois d'événements banalisés ou définir un point de soudure spécifique de plus haut niveau. Une vue est en général un assemblage de composants gérant leur propre modèle et doit alors transmettre au contrôleur des événements d'assez haut niveau.
Synthèse et exemples
Dans la pratique, il est difficile de définir des objets complexes
sans faire intervenir de dépendances réciproques.
Des outils de modélisation comme UML facilitent l'identification des points de soudure
entre objets et l'optimisation des interactions.
L'usage de dépendances réciproques rend les objets difficiles à comprendre,
à maintenir et à corriger, et doit être évité,
même si la conception initiale en est rendue plus délicate.
Les composants Java Swing utilisent une architecture MVC simplifiée
dans laquelle le contrôleur et la vue sont rassemblés en un "UIDelegate
".
Les principes de l'architecture restent valables.
Un composant Swing utilise souvent plusieurs modèle et plusieurs sous-composants
Quelques éléments d'un tableau Swing JTable
- la description des colonnes est déléguée à un
ColumnModel
- le contenu des cellules est géré par un
TableModel
- la liste des lignes, colonnes et cellules sélectionnées
est déléguée à un
ListSelectionModel
- le dessin des cellules en consultation est délégué à un
Renderer
- la gestion éventuelle de la cellule en cours de saisie est déléguée à un
Editor
- de multiples gestionnaires d'événements sont utilisés en interne
et pour informer les objets intéressés de toute sélection ou saisie.
L'utilisation de modèles et normes de développements standardisés est la seule solution
pour gérer harmonieusement la multitude d'objets collaborant dans un environnement graphique.
Quand cette démarche est bien menée, elle assure une bonne réutilisation des concepts
et objets unitaires
- la plupart des swings utilisent un
Renderer
, capable de dessiner
un objet -le plus souvent une chaine de caractères- dans une zone rectangulaire
- tous les objets graphiques permettant de sélectionner quelque chose dans une liste
utilisent un
ListSelectionModel
. Chaque version fonctionne selon
un principe et une intreface générique et présente des comportements spécialisés.
Patrick Larreya, Janvier 2001.