Génie Logiciel, FISE2-INFO4
TD Modèle-Vue-Contrôleur
Frédérique Laforest & Guillaume Muller
Exercices d’entraînement à faire durant le TD. . . et après.
1 Introduction : MVC et [Link]
Cette section est reprise du TP de Lilian Casiez, Université Lille 1. Il a été modifié et simplifié.
Ce TP est également disponible sur MooTSE, dans la section FISE2 Génie Logiciel. Télé-
charger le code associé sur MooTSE.
1.1 Objectif
L’objectif est de créer une interface permettant le contrôle de la température en degrés Celsius
ou Fahrenheit. L’interface se compose de trois vues représentant la même température sous des
formes différentes. La modification d’une des vues doit mettre automatiquement à jour les autres
vues. Nous allons voir comment appliquer le modèle MVC pour réaliser cette interface.
Fig. 1: Trois vues pour un même modèle
1.2 Préambule
Le modèle MVC comprend :
1. Un modèle. Celui-ci comprend les données et un certain nombre de méthodes pour les
lire et les modifier. Le modèle n’a aucune connaissance de la façon dont les données sont
présentées à l’utilisateur (la Vue). Le modèle peut toutefois enregistrer une ou plusieurs
vues (une liste de dépendants) qui sont notifiées quand les données du modèle ont subi
une modification (patron de conception Observateur). En Java, le modèle comprend une
ou plusieurs classes de type [Link] (ou d’une interface ou
classe équivalente).
2. Une ou plusieurs vues. Une vue fournit une représentation graphique de tout ou partie
des données du modèle. Chaque vue obtient les données à afficher en en faisant la de-
mande au modèle. Quand un utilisateur manipule une vue d’un modèle, la vue informe
1
2 1 Introduction : MVC et [Link]
le contrôleur du changement désiré. En Java, les vues doivent implémenter l’interface
[Link] (ou interface équivalente).
3. Plusieurs contrôleurs. Les vues sont associées à des contrôleurs qui mettent à jour
le modèle. Chaque vue est associée à un unique contrôleur. Le contrôleur interprète les
actions de l’utilisateur (par exemple augmenter la température) et appelle les méthodes
requises du modèle pour le mettre à jour. Le modèle informe alors toutes les vues qu’un
changement est survenu. Ces dernières se mettent à jour. Il peut aussi vérifier les actions
de l’utilisateur et modifier la vue en conséquence si besoin.
Fig. 2: Le modèle MVC
1.3 Explications de l’organisation MVC du code de l’exemple
1.3.1 Le modèle
Le modèle possède un élément de type [Link]. Cette classe va
gérer pour nous la liste des objets à notifier de changements, ainsi que de tous les notifier quand
nous le lui demanderons. Nous appellerons firePropertyChange() chaque fois que nous modifie-
rons les données du modèle. La méthode firePropertyChange() notifie chaque objet enregistré
pour recevoir une notification (ici les vues) qu’un changement d’état du modèle a eu lieu. Elle per-
met également de passer des informations supplémentaires concernant le changement d’état (va-
riante push du patron de conception Observateur). La méthode addPropertyChangeListener()
permet d’enregistrer les objets à notifier dans la liste maintenue par [Link].
Voici la classe du modèle correspondant à notre interface :
package model;
import [Link];
import [Link];
public class TemperatureModel {
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
public void addPropertyChangeListener(PropertyChangeListener listener) {
[Link](listener);
}
private double temperatureC = 20;
public double getC () {
return temperatureC ;
}
1.3 Explications de l’organisation MVC du code de l’exemple 3
public void setC (double tempC) {
double oldVal = temperatureC;
temperatureC = tempC ;
[Link]("temperatureC", oldVal, temperatureC);
}
public double getF () {
return temperatureC * 9.0 / 5.0 + 32.0;
}
public void setF (double tempF) {
double oldVal = temperatureC;
temperatureC = (tempF - 32) * 5.0 / 9.0;
[Link]("temperatureC", oldVal, temperatureC);
}
}
1.3.2 Les vues
Il est possible de créer une ou plusieurs vues. Chaque vue doit implémenter l’interface
[Link] et par conséquent implémenter la méthode propertyChange().
Le premier paramètre permet d’identifier le sujet à l’origine de la mise à jour et le second permet
de recevoir des informations supplémentaires concernant la mise à jour. Chaque vue a besoin
de connaître le modèle et le contrôleur. La connaissance du modèle permet d’enregistrer la vue
auprès de ce dernier et d’appeler les méthodes du modèle pour mettre à jour la vue. Les actions
de l’utilisateur sur l’interface sont envoyées au contrôleur.
Comme nous avons deux vues très semblables, nous définissons une classe abstraite qui ras-
semble tous les éléments communs aux deux vues semblables. La troisième vue ne réutilisera pas
cette classe abstraite car trop différente. Voici la classe abstraite qui concerne les deux premières
vues :
package vue;
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
public abstract class TemperatureVue implements PropertyChangeListener {
//private String label;
protected TemperatureModel model;
protected TemperatureController controller;
private JFrame temperatureJFrame;
private JTextField display = new JTextField();
private JButton upJButton = new JButton("+");
private JButton downJButton = new JButton("-");
4 1 Introduction : MVC et [Link]
public TemperatureVue(String label, TemperatureModel model,
TemperatureController controller, int posX, int posY) {
[Link] = model;
[Link] = controller;
temperatureJFrame = new JFrame(label);
[Link](new JLabel(label), [Link]);
[Link](display, [Link]);
JPanel panelbuttons = new JPanel();
[Link](downJButton);
[Link](upJButton);
temperatureJFrame .add(panelbuttons, [Link]);
[Link](JFrame.EXIT_ON_CLOSE);
// We register the View as a listener of events from the Model
[Link](this);
[Link](200, 100);
[Link] (posX, posY);
[Link](true);
}
public void setDisplay(String s) {
[Link] (s);
}
public void enableWarningColor() {
[Link]([Link]);
}
public void disableWarningColor() {
[Link]([Link]);
}
public double getDisplay() {
double result = 0.0;
try {
result = [Link]([Link]()).doubleValue();
}
catch(NumberFormatException e) {
[Link]("String to Double Conversion error:" +
[Link]());
}
return result;
}
public void addDisplayListener(ActionListener a) {
[Link](a);
}
public void addUpListener(ActionListener a) {
[Link](a);
}
public void addDownListener(ActionListener a) {
[Link](a);
}
}
Les classes TemperatureVueCelsius et TemperatureVueFahrenheit dérivent de TemperatureVue
et implémentent la méthode propertyChange(). Voici la classe TemperatureVueCelsius.
package vue;
import [Link];
1.3 Explications de l’organisation MVC du code de l’exemple 5
import [Link];
import [Link];
import [Link];
import [Link];
public class TemperatureVueCelsius extends TemperatureVue {
public TemperatureVueCelsius(TemperatureModel modele, TemperatureController
controleur, int posX, int posY) {
super(" Temperature Celsius ", modele, controleur, posX, posY);
setDisplay(""+[Link]());
addUpListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
[Link]();
}});
addDownListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
[Link]();
}});
addDisplayListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
double tempC = getDisplay();
[Link](tempC);
}});
}
public void propertyChange(PropertyChangeEvent e) {
// Here we can use the values in the Event (Java8+ facility)
// Or query the model for the new value (Strict respect of the MVC
DesignPattern)
setDisplay(""+[Link]());
}
}
1.3.3 Les contrôleurs
Chaque vue est associée à un contrôleur personnel (un contrôleur par vue). Chaque contrôleur
interprète les actions de l’utilisateur et met à jour le modèle. Le contrôleur peut utiliser le patron
de conception Stratégie pour mettre en œuvre différentes stratégies d’interprétation des actions
de l’utilisateur. Dans l’exemple ci-dessous, nous pouvons augmenter la température d’un degré
quand on clique sur le bouton + mais il doit être possible de modifier la façon dont la température
augmente ou diminue.
Enfin, le contrôleur doit également avoir connaissance de la vue qui lui est associée pour
éventuellement modifier cette dernière. En effet, des actions de l’utilisateur sur la vue peuvent
avoir pour conséquence de modifier la vue sans pour autant modifier le modèle. Cela se fait
par un appel de méthode de la vue. Dans l’exemple ci-dessous, nous faisons passer le champ de
texte en rouge quand la température dépasse 400C par un appel dans la méthode control de la
méthode enableWarningColor() définie dans la vue. Autre exemple, on pourrait de la même
façon ajouter un bouton qui, quand on clique dessus dans la vue, modifie la couleur de l’arrière
plan de l’interface. Le contrôleur pourrait aussi vérifier que le texte saisi par l’utilisateur dans les
champs de texte correspond bien à un nombre et, le cas échéant, informer la vue qu’une erreur
de saisie a eu lieu.
6 1 Introduction : MVC et [Link]
package control;
import [Link];
import [Link];
public class TemperatureController {
private TemperatureModel model ;
private TemperatureVue view = null ;
public TemperatureController(TemperatureModel m) {
model = m;
}
public void augmenteDegresC(){
[Link]([Link]()+1);
control();
}
public void diminueDegresC(){
[Link]([Link]()-1);
control();
}
public void fixeDegresC(double tempC){
[Link](tempC);
control();
}
public void augmenteDegresF(){
[Link]([Link]()+1);
control();
}
public void diminueDegresF(){
[Link]([Link]()-1);
control();
}
public void fixeDegresF(double tempF){
[Link](tempF);
control();
}
private void control() {
if(view != null) {
if([Link]() > 40.0) {
[Link]();
} else {
[Link]();
}
}
}
public void setView(TemperatureVue view) {
[Link] = view ;
}
}
1.4 Intégration de l’ensemble
La classe TemperatureMVC permet de créer l’ensemble des éléments MVC, et de les associer.
import [Link];
1.5 À faire 7
import [Link];
import [Link];
import [Link];
public class TemperatureMVC {
public TemperatureMVC() {
TemperatureModel tempmod = new TemperatureModel();
TemperatureController tempcontrolC = new TemperatureController(tempmod);
TemperatureController tempcontrolF = new TemperatureController(tempmod);
TemperatureVueCelsius pvc = new TemperatureVueCelsius(tempmod,
tempcontrolC, 100, 200);
TemperatureVueFarenheit tvf = new TemperatureVueFarenheit(tempmod,
tempcontrolF, 100, 350);
[Link](pvc);
[Link](tvf);
}
public static void main(String args []) {
new TemperatureMVC();
}
}
1.5 À faire
— Étudier les explications dans le texte ci-dessus.
— Tester l’utilisation de l’exemple de code Java fourni.
— Faire un diagramme de classes UML à partir du code Java fourni
— Faire un diagramme de séquence retraçant les appels de méthodes au lancement de l’ap-
plication
— Faire un diagramme de séquence depuis une saisie de valeur par l’utilisateur.
2 Exercice : Rectangle
L’objectif de cet exercice est de faire une application dessinant un rectangle dont la hauteur
est modifiable par l’utilisateur (voir l’annexe pour un début de code). L’application comporte :
— un modèle pour stocker les informations sur le rectangle.
— deux vues : l’une permet la modification de la hauteur, l’autre trace le rectangle (cf.
annexe en fin de sujet pour découvrir comment tracer un rectangle).
— un contrôleur, qui transmet les modifications de hauteur saisies par l’utilisateur au modèle
dans le cas où ces modifications sont correctes (valeur de hauteur entre un min et un max
définis dans le modèle), et change l’interface de saisie de hauteur dans le cas contraire.
Un exemple de fonctionnement est disponible sous forme d’un fichier jar sur MooTSE dans
la section correspondant à ce TD. Dans cet exemple, les min et max sont respectivement fixés à
0 et 399. En vous inspirant du code de la partie 1 de ce sujet, développez votre propre version
de cet exercice.
A Annexe : dessiner un rectangle de taille fixe – à adapter
public class RectanglePanel extends JPanel {
public RectanglePanel() {
super();
}
public Dimension getPreferredSize() {
8 A Annexe : dessiner un rectangle de taille fixe – à adapter
return new Dimension(200, 200);
}
public void paintComponent(Graphics g) {
[Link](g);
[Link](10, 10, 200, 100);
}
public void refaire() {
[Link](0);
}
}