Les interfaces utilisateurs sont des parties essentielles de toute application, car, de fait, l’utilisateur interagit avec elles. Si un utilisateur trouve qu’une interface est difficile à gérer ou à comprendre, il aura un avis négatif sur l’ensemble de l’application. De plus, les styles et les fonctionnalités changent avec le temps.
De plus, les exigences d’aujourd’hui nécessitent que les applications s'adaptent à différentes tailles d'écran (ordinateur, tablette, smartphone…), ce qui exige un design responsive. Autrement dit, il faut avoir un moyen de connaître les dimensions mais aussi l’orientation (portrait ou paysage) de l’écran, pour générer la meilleure configuration graphique possible. Il est préférable de gérer le design responsive via HTML et le CSS.
Je pensais que ce cours portait sur Java ! Combien dois-je en savoir sur le HTML et le CSS ?
Juste un petit peu. Vous avez raison, les finitions de la mise en page n’entrent pas dans le périmètre de ce cours. Cela dit, la séparation de la couche d’interface utilisateur, afin de permettre au CSS et au HTML de changer indépendamment du reste du code, correspond au changement architectural que nous recherchons.
Pour suivre le rythme des exigences en constante évolution, nous devons placer l’interface dans sa propre couche.
Nous réparerons ceci dans notre application en :
examinant l’architecture et le code existants pour voir où se situent les problèmes (à l'aide de nos instruments de mesure de la première partie).
définissant nos solutions pour les appliquer à notre architecture.
appliquant notre solution au code.
Examinez l’architecture existante pour voir où se situent les problèmes
Commençons par regarder l’architecture actuelle :
Nous voyons que tout ce avec quoi l’utilisateur interagit (client, réservation, avion, pilote) a une connexion directe avec un ensemble de composants Java Swing. Regardons un extrait de la classe com.airbusiness.program.swing.Client
de plus près :
public static void fillTableWithData(JTable table) {
table.setModel(buildTableModel());
}
private static DefaultTableModel buildTableModel() {
ResultSet rs = AirBusinessDb.getResultSet("SELECT * from clients");
try {
ResultSetMetaData metaData = rs.getMetaData();
Vector<String> columnNames = new Vector<String>();
int columnCount = metaData.getColumnCount();
for (int column = 1; column <= columnCount; column++) {
columnNames.add(metaData.getColumnName(column));
System.out.println("columnName: " + metaData.getColumnName(column));
}
// data of the table
Vector<Vector<Object>> data = new Vector<Vector<Object>>();
while (rs.next()) {
Vector<Object> vector = new Vector<Object>();
for (int columnIndex = 1; columnIndex <= columnCount; columnIndex++) {
vector.add(rs.getObject(columnIndex));
System.out.println("value [" + columnIndex + "][" + rs.getObject(columnIndex) + "]");
}
data.add(vector);
}
return new DefaultTableModel(data, columnNames);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
Quel est le problème ici ?
Avant de vous lancer et de regarder ma réponse, voyez si vous pouvez utiliser certains instruments de mesure pour analyser le code. Essayez d’utiliser les principes SOLID et certaines de nos questions clés.
Alors, avez-vous fini votre analyse ?
Par exemple, que se passe-t-il si nous changeons la façon dont nous voulons montrer l’ensemble des clients ? La classe devra être modifiée pour montrer les données différemment.
De plus, cette méthode est difficile à tester. Un objet JTable doit être construit, alors qu’un test unitaire ne doit pas avoir besoin d’une interface utilisateur. Le résultat (c’est-à-dire les lignes remplies) est également contenu dans le JTable. Notre test unitaire aurait besoin de requêter les lignes du JTable pour valider que c’est correct. 😱
Et enfin, si nous voulions changer juste un peu la présentation des données client (comme la largeur des colonnes, ou le passage de certaines données en gras), nous devrons entrer dans cette classe, et modifier comment elle construit chaque ligne et cellule du tableau. Cette fonctionnalité n’a pas sa place ici.
Définissez des solutions
La première moitié de notre changement d’architecture suivra le principe de responsabilité unique. L’interface utilisateur et les classes d’application (client, réservation, avion) doivent être séparées. La classe client (réservation et pilote) ne connaîtra plus rien sur JTable. Elle sera uniquement responsable de la maintenance de ses propres données. Nous commencerons à travailler là-dessus en coupant toutes les connexions directes entre nos classes Java et les classes Java Swing.
La deuxième moitié de notre changement consistera à placer la vue dans sa propre couche isolée. Elle ne sera plus contrôlée par de nombreuses classes différentes.
Après cette modification, notre architecture ressemblera à ceci :
Appliquez nos solutions
Tout d’abord, nous devons séparer ces classes. Une manière facile de remplacer notre dépendance Java Swing est d’utiliser Spring Boot et la bibliothèque de templates Thymeleaf. Avec ces outils, nous allons convertir l’interface Java Swing en interface web. Nous pouvons commencer par construire une nouvelle application. Allez sur https://start.spring.io :
Et maintenant, renseignez les informations suivantes :
Project: Maven Project
Language: Java
Spring Boot: 2.4.4
Group: com.airbusiness
Artifact: airbusiness-mvc
Dependencies: Thymeleaf, Spring Web, SpringBoot Dev Tools
Spring Boot DevTools offre, entre autres, la possibilité de déployer à chaud votre application (vos modifications seront buildées et déployées automatiquement!).
Quand vous avez fini, cliquez sur « Generate ».
Une fois l’application terminée, ajoutez une interface utilisateur HTML simple. Créez le fichier index.html dans le dossier src/main/resources/templates. Le fichier doit contenir ce qui suit :
<!DOCTYPE HTML>
<html>
<head>
<title>AirBusiness</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<a href="/index">
<img th:src="@{/images/AirBusinessLogo.jpg}" height="64" width="64"/>
</a>
<p>Welcome to Air Business</p>
</body>
</html>
Ensuite, copiez ce fichier dans src/main/resources/static/images/AirBusinessLogo.jpg. Exécutez le projet. Connectez-vous avec un navigateur en vous rendant sur http://localhost:8080.
Nous avons maintenant une application web, qui fait une chose simple mais avec une vue séparée !
Séparez vos inputs
Les interfaces sont constituées d’un côté input et d’un côté output. Du côté input, il est utile de penser à l’interface utilisateur comme générateur d’événements. Presque tout ce que fait l’utilisateur avec l’interface doit être un événement qui revient vers le contrôleur.
Par exemple, dans l’implémentation d’origine, nous avons un bouton d’écoute des événements. Il configure l’objet JTable pour montrer l’ensemble d’items correspondant.
JButton btn = new JButton("Clients");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Client.fillTableWithData(table);
}
});
Ce n’est pas très élégant, mais c’est à prévoir quand on construit une interface avec Swing.
Spring Boot effectuera la connexion pour nous. Mais pour l’instant, nous allons simplement ajouter quelques boutons à l’interface utilisateur, qui nous amèneront vers des pages (qui n’existent pas encore).
Dans le fichier index.html, ajoutez ce qui suit sous la ligne <p>Welcome to Air Business</p>
:
<p>Choose from the following</p>
<p><a href="/reservations">Reservations</a></p>
<p><a href="/clients">Clients</a></p>
<p><a href="/maintenance">Pilots Submit Maintenance Request</a></p>
<p><a href="/maintenance">Mechanics Update Maintenance Request</a></p>
Testez l’application à nouveau. Vous remarquerez peut-être que, si vous cliquez sur le logo, ou tout autre bouton, vous obtenez une page d’erreur générée par Spring Boot. C’est parce que ces pages n’existent pas encore. Ne vous inquiétez pas, nous les ajouterons bientôt.
Nous avons maintenant accompli les premières étapes de notre modification MVC ! Nous avons identifié quel code sera difficile à modifier, tester ou maintenir. Nous avons introduit une couche de vue qui n’est pas directement contrôlée par les classes de notre application. Et nous avons tiré profit d’un framework MVC pour nous simplifier le travail.
En résumé
Il faut identifier le code qui sera difficile à modifier, tester, ou maintenir.
On procède à une extraction de la couche de vue par rapport au reste.
La couche n’est pas directement contrôlée par les classes d’application.
L’aspect input peut être générateur d’événements.
Le framework MVC permet de simplifier la refactorisation.
Maintenant que nous avons extrait la vue, passons au modèle !