4PRJ1D - Théorie

Gestion de projet 1

Jonathan Lechien

Bruxelles, février 2026

Haute École Bruxelles-Brabant : Département des Sciences Informatiques
JLC - 4PRJ1D

Objectifs pédagogiques

  • Analyser les exigences et planifier le développement du projet.
  • Concevoir, développer et tester une application complète.
  • Justifier ses choix techniques et proposer des améliorations.
  • Critiquer de manière réfléchie son analyse à la fin du développement,
JLC - 4PRJ1D

Matières

Développement Java

  1. FXML et Scene Builder
  2. Multi Threading
  3. JDBC et JPA
  4. Librairie externe

Conception et gestion de projet

  1. Repository pattern
  2. Architectural pattern
  3. Tester une application
  4. Présenter une analyse
JLC - 4PRJ1D

Organisation de l'unité

  • 2H par semaine de théorie
  • 4H par semaine de laboratoire
  • Planning détaillé disponible sur PoEsi
JLC - 4PRJ1D

L'évaluation repose sur un contrôle continu structuré autour de la réalisation d’un projet en binôme. Les étudiants doivent montrer l’évolution régulière dans la réalisation des TDs et du projet : 20% de la note. Une défense individuelle sera organisé durant la session d'examen : 80% de la note

JLC - 4PRJ1D

Interface utilisateur avec Scene Builder

Utilisation de fichiers FXML

JLC - 4PRJ1D

Une approche déclarative pour JavaFX

  • Séparation entre logique et interface utilisateur
  • Facilite la collaboration entre développeurs et designers
  • Lisibilité améliorée du code
  • Réutilisation et modularité
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>

<?import javafx.scene.control.Button?>

<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml">
    <padding>
        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
    </padding>

    <Label text="Bonjour, FXML!" />
    <Button text="Cliquez-moi" onAction="#onHelloButtonClick"/>

</VBox>
Code: FXML Hello World.
JLC - 4PRJ1D

Qu'est-ce que le FXML ?

  • Un langage basé sur XML permettant de définir des interfaces JavaFX
  • Chargé et interprété à l'exécution par JavaFX
  • Permet de créer des interfaces graphiques dynamiques

Exemple :

<VBox xmlns="http://javafx.com/javafx" 
      xmlns:fx="http://javafx.com/fxml">
    <Label text="Bonjour, FXML!" />
    <Button text="Cliquez-moi" />
</VBox>
JLC - 4PRJ1D

Balises et équivalence avec JavaFX

  • <VBox> équivaut à new VBox() en Java
  • <Label text="Texte"/> équivaut à new Label("Texte")
  • <Button> équivaut à new Button()

Exemple JavaFX classique :

VBox vbox = new VBox();
Label label = new Label("Bonjour, JavaFX!");
Button button = new Button("Cliquez-moi");
vbox.getChildren().addAll(label, button);
JLC - 4PRJ1D

Transformation d'une balise VBox

  • On peut enrichir cette balise avec des attributs pour mieux contrôler l'affichage :
    • alignment="CENTER" : Centre les éléments à l'intérieur de la VBox.
    • spacing="20.0" : Définit un espacement de 20 pixels entre les éléments.

FXML

<VBox alignment="CENTER" 
      spacing="20.0" 
      xmlns:fx="http://javafx.com/fxml">

Equivalent Java

VBox vbox = new VBox();
vbox.setAlignment(Pos.CENTER);
vbox.setSpacing(20.0);
JLC - 4PRJ1D

Import et première balise en FXML

  • Un fichier FXML commence toujours par une balise de conteneur racine : la root .
  • La root est de type VBox dans l'exemple.
  • L'attribut xmlns est obligatoire et définit l'espace de noms XML utilisé.
  • L'attribut xmlns:fx est utilisé pour les fonctionnalités spécifiques à JavaFX.

Exemple :

<VBox xmlns="http://javafx.com/javafx" 
      xmlns:fx="http://javafx.com/fxml">
  • On doit importer des classes Java directement dans FXML :
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
JLC - 4PRJ1D

Déclaration XML en FXML

  • Chaque fichier FXML commence par une déclaration XML standard.
  • Cette déclaration spécifie la version XML et l'encodage utilisé.
  • Obligatoire pour assurer la compatibilité avec les analyseurs XML.

Exemple :

<?xml version="1.0" encoding="UTF-8"?>
  • version="1.0" : Définit la version XML utilisée.
  • encoding="UTF-8" : Spécifie l'encodage des caractères, essentiel pour la prise en charge des accents et caractères spéciaux.
JLC - 4PRJ1D

Dépendances Maven pour JavaFX et FXML

Dans le fichier pom.xml, ajoutez les dépendances suivantes :

<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-controls</artifactId>
    <version>25.0.2</version>
</dependency>
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-fxml</artifactId>
    <version>25.0.2</version>
</dependency>
  • javafx-controls : Contient les composants UI JavaFX
  • javafx-fxml : Permet d'utiliser FXML dans l'application
JLC - 4PRJ1D

SceneBuilder : Interface graphique pour créer du FXML

Figure: Scene Builder.
  • Permet de générer du FXML visuellement
  • Facile d'utilisation pour les non-développeurs
  • Génère automatiquement les IDs et méthodes associées
JLC - 4PRJ1D

Structure d'un projet Maven avec FXML - Version 1

my-javafx-app/
│-- src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/app/
│   │   │       ├── Main.java
│   │   ├── resources/
│   │   │   └── com/example/app/
│   │   │       ├── view.fxml
│-- pom.xml
  • Main.java : Classe principale pour lancer l'application JavaFX.
  • view.fxml : Définit l'interface graphique.
JLC - 4PRJ1D

Charger un fichier

public class Main extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        URL resource = Main.class.getResource("hello-view.fxml");
        FXMLLoader loader = new FXMLLoader(resource);
        Parent root = loader.load();
        Scene scene = new Scene(root, 640, 800);
        stage.setTitle("Hello!");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}
JLC - 4PRJ1D

Une nouvelle classe : le Contrôleur FXML

  • Lien entre FXML et logique Java
  • Utilisation de FXMLLoader pour charger le fichier FXML
  • Annotation @FXML pour lier les composants
  • @FXML est utilisé pour injecter l'élément défini dans le fichier FXML.
  • Gestion des événements avec EventHandler
JLC - 4PRJ1D

Structure d'un projet Maven avec FXML - Version 2

my-javafx-app/
│-- src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/app/
│   │   │       ├── Main.java
│   │   │       ├── FxmlController.java
│   │   ├── resources/
│   │   │   └── com/example/app/
│   │   │       ├── view.fxml
│-- pom.xml
  • Main.java : Classe principale pour lancer l'application JavaFX.
  • Controller.java : Gère la logique associée aux événements FXML.
  • view.fxml : Définit l'interface graphique.
JLC - 4PRJ1D

Utilisation de fx:id

  • fx:id permet d'identifier un élément du fichier FXML pour y accéder dans le contrôleur.

Exemple :

<VBox xmlns:fx="http://javafx.com/fxml">
    <Button fx:id="myButton" text="Cliquez-moi"/>
</VBox>

Accès en Java :

public class Controller {
    @FXML
    private Button myButton;
}
JLC - 4PRJ1D

Utilisation de fx:controller

fx:controller spécifie la classe Java qui gère la logique de l'interface.

Exemple :

<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="com.example.FxmlController">
    <Button fx:id="myButton" text="Cliquez-moi"/>
</VBox>

Accès en Java :

public class FxmlController {
    @FXML
    private Button myButton;
}
JLC - 4PRJ1D

La méthode initialize() du contrôleur

  • La méthode initialize() est automatiquement appelée après l'instanciation du contrôleur.
  • Elle permet d'initialiser des composants.

Exemple :

public class FxmlController {
    @FXML private Label label;

    public void initialize() {
        label.setText("Bienvenue dans l'application!");
    }
}
JLC - 4PRJ1D

Notions avancées : TableView et ObservableList

  • Une ObservableList est une liste dynamique qui permet de notifier les changements à l'interface utilisateur.
  • Utile pour gérer des données affichées dans des composants comme TableView.
  • TableView est un composant permettant d'afficher des données tabulaires.
JLC - 4PRJ1D

Déclaration d'un TableView en FXML

<TableView fx:id="personTable" xmlns:fx="http://javafx.com/fxml">
    <columns>
        <TableColumn text="Nom" fx:id="nameColumn" />
        <TableColumn text="Âge" fx:id="ageColumn" />
    </columns>
</TableView>
JLC - 4PRJ1D

Contrôleur associé à une TableView en FXML

public class FxmlController {
    @FXML private TableView<Person> personTable;
    @FXML private TableColumn<Person, String> nameColumn;
    @FXML private TableColumn<Person, Integer> ageColumn;

    public void initialize() {
        ObservableList<String> items = 
            FXCollections.observableArrayList(person1, person2);
        nameColumn.setCellValueFactory(
            new PropertyValueFactory<>("name"));
        ageColumn.setCellValueFactory(
            new PropertyValueFactory<>("age"));
        personTable.setItems(items);
    }
}
JLC - 4PRJ1D

Import de composants de org.controlsfx

  • Bibliothèque JavaFX avancée
  • SearchableComboBox pour une liste déroulante avec recherche intégrée

Exemple :

SearchableComboBox<String> comboBox = new SearchableComboBox<>();
comboBox.getItems().addAll("Option 1", "Option 2");
JLC - 4PRJ1D

Navigation entre vues et patterns

  • Utilisation de FXMLLoader pour charger dynamiquement les vues
  • Patterns recommandés : MVC ou ses variants

Exemple de changement de scène :

Parent newView = FXMLLoader.load(getClass().getResource("view.fxml"));
stage.setScene(new Scene(newView));
JLC - 4PRJ1D

FXML et CSS dans src/main/java/resources

styles.css

.root {
    -fx-background-color: lightgray;
}

.button {
    -fx-background-color: blue;
    -fx-text-fill: white;
}

hello-world.fxml

<VBox xmlns="http://javafx.com/javafx"
      xmlns:fx="http://javafx.com/fxml"
      stylesheets="@styles.css">
    <Button text="Bouton Stylisé" 
            styleClass="button"/>
</VBox>
  • stylesheets="@styles.css" : Charge le fichier CSS.
  • styleClass="button" : Applique la classe CSS définie.
JLC - 4PRJ1D

Structure d'un projet Maven avec FXML - Version 3

my-javafx-app/
│-- src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/app/
│   │   │       ├── Main.java
│   │   │       ├── FxmlController.java
│   │   ├── resources/
│   │   │   └── com/example/app/
│   │   │       ├── view.fxml
│   │   │       ├── styles.css
│   │   │       ├── images/
│-- pom.xml
JLC - 4PRJ1D

References

OpenJFX. OpenJFX. Consulté le 1er février 2025. https://openjfx.io/.

OpenJFX. JavaDoc 23. Consulté le 1er février 2025. https://openjfx.io/javadoc/23/.

GluonHQ. Scene Builder. Consulté le 1er février 2025. https://gluonhq.com/products/scene-builder/.

ControlsFX. ControlsFX. Consulté le 1er février 2025. https://controlsfx.github.io/.

OpenJFX. Référence CSS JavaFX. Consulté le 1er février 2025. https://openjfx.io/javadoc/23/javafx.graphics/javafx/scene/doc-files/cssref.html.

JLC - 4PRJ1D

Programmation concurrente

Les threads en java

JLC - 4PRJ1D

Introduction

Définition

  • Un thread est une unité d'exécution au sein d'un processus.
  • Permet l'exécution parallèle de tâches.

Avantages du multithreading en Java

  • Meilleure utilisation des ressources.
  • Réduction du temps d'attente pour certaines tâches.
  • Amélioration de la réactivité des applications.
JLC - 4PRJ1D

Le cycle de vie d'un thread

JLC - 4PRJ1D

Création d'un Thread

  1. Héritage de la classe Thread
  2. Implémentation de l'interface Runnable
  3. Composition via un attribut Thread
JLC - 4PRJ1D

Création d'un Thread

Héritage de la classe Thread

class MyThread extends Thread {
    public void run() {
        System.out.println("Thread en cours d'exécution");
    }
}

MyThread t = new MyThread();
t.start();

Lien vers les codes exemples utilisés lors de la présentation

JLC - 4PRJ1D

Création d'un Thread

Implémentation de l'interface Runnable

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Thread en cours d'exécution");
    }
}

Thread t = new Thread(new MyRunnable());
t.start();
JLC - 4PRJ1D

Création d'un Thread

Composition via un attribut

public class MyThreadComposition {

    private Thread thread;

    public MyThreadComposition() {
        thread = new Thread(new MyRunnable());
    }

    public void start() {
        thread.start();
    }

}
JLC - 4PRJ1D

Différences entre la méthode start et run

  • start() : démarre l'exécution du thread, elle appelle automatiquement la méthode run() dans un nouveau thread d'exécution.

  • run() : méthode qui contient le code à exécuter dans le thread. Si vous définissez une classe qui implémente Runnable ou étend Thread, vous redéfinissez cette méthode pour spécifier ce que doit faire le thread. Si vous appelez directement run(), le code sera exécuté dans le thread principal, pas dans un nouveau thread.

JLC - 4PRJ1D

Threads Démon

Un thread démon s'exécute en arrière-plan et ne bloque pas la fermeture de l'application.

Thread daemonThread = new Thread(() -> {
    while (true) {
        System.out.println("Daemon thread en cours");
    }
});

daemonThread.setDaemon(true);
daemonThread.start();
JLC - 4PRJ1D

Attendre la fin d'un Thread avec la méthode join()

public class MonThread extends Thread {

    @Override
    public void run() {

        try {

            System.out.println("Le thread commence");

            // Simuler un travail en suspendant 
            // le thread  pendant 2 secondes
            Thread.sleep(2000);

            System.out.println("Le thread se " +
              " termine après 2 secondes.");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}


public static void main(String[] args) {

    MonThread monThread = new MonThread();
    monThread.start();

    try {

        System.out.println(
          "Main attend la fin du thread");

        monThread.join();

        System.out.println(
          "Main reprend après la fin du thread");

    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println(
      "Le programme principal se termine.");
}
JLC - 4PRJ1D

Quelle est la valeur affichée ?

public class MonCompteur
    implements Runnable {

    // Variable partagée entre les threads
    private static int compteur = 0;

    private void incrementer() {
        compteur++;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            incrementer();
        }
    }
}
public static void main(String[] args) 
    throws InterruptedException {

    MonCompteur monCompteur = new MonCompteur();
    Thread t1 = new Thread(monCompteur);
    Thread t2 = new Thread(monCompteur);

    // Démarrer les threads
    t1.start();
    t2.start();

    // Attendre la fin des deux threads
    t1.join();
    t2.join();

    // Afficher la valeur finale du compteur
    System.out.println(
        "Valeur du compteur : " + compteur);
}
JLC - 4PRJ1D

Synchronisation et gestion de la concurrence

Méthode synchronisée

L'accès à cette méthode est limité à un seul thread à la fois.

private synchronized void incrementer() {
    compteur++;
}
JLC - 4PRJ1D

Synchronisation et gestion de la concurrence

Bloc synchronisé

public void incrementer() {
    // Code non synchronisé
    //avant le bloc
    
    // Bloc synchronisé
    synchronized(this) {
        compteur++;
    }
}
JLC - 4PRJ1D

Synchronisation et gestion de la concurrence

Synchronisation sur des objets spécifiques

public class MonCompteur
    implements Runnable {

    private static int compteur = 0;

    private final Object verrou = new Object();  // Un objet pour le verrouillage

    public void incrementer() {
        synchronized(verrou) {
            compteur++;
        }
    }
}
JLC - 4PRJ1D

L'étreinte mortelle - Deadlock

Vision séquentielle des accès aux ressources, en réalité les actions sont concourantes.
JLC - 4PRJ1D

Structures thread-safe

Structures de données conçues pour être utilisées en toute sécurité dans des environnements multithreads sans nécessiter de synchronisation externe.

Exemples :

  1. SynchronizedList, SynchronizedSet, SynchronizedMap
  2. ConcurrentSkipListMap, ConcurrentSkipListSet
  3. CopyOnWriteArrayList, CopyOnWriteArraySet
  4. Vector
JLC - 4PRJ1D

Lister les Threads

Thread.getAllStackTraces() retourne les threads en cours d'exécution.

Nom du Thread Rôle
main Le thread principal qui exécute la méthode main()
Reference Handler Gère les références
Finalizer Appelle la méthode finalize()
Signal Dispatcher Gère les signaux du système d'exploitation

Remarque : Le nombre exact peut varier selon la version de la JVM et l'environnement d'exécution.

JLC - 4PRJ1D

Threads et JavaFX

  • Main Thread démarre l'application.
  • JavaFX Application Thread gère l'UI.
  • Une exception est lancée si une mise à jour est effectuée hors du JavaFX Application Thread,
Exception in thread "Thread-XX" java.lang.IllegalStateException:
Not on FX application thread; currentThread = Thread-XX
JLC - 4PRJ1D

Threads et JavaFX

Solution avec Platform.runLater()

button.setOnAction(event -> {
    new Thread(() -> {
        try {
            Thread.sleep(2000);

            label.setText("Traitement "
                + " terminé !");  

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
});

button.setOnAction(event -> {
    new Thread(() -> {
        try {
            Thread.sleep(2000);

            Platform.runLater(()-> 
              label.setText("Traitement "
              + " terminé !"));

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
});
JLC - 4PRJ1D

Threads et JavaFX

Solution avec un Task

Task<Void> task = new Task<>() {
        @Override
        protected Void call() throws Exception {
            label.setText("Mise à jour UI")
            return null;
        }
    };

// Liaison des propriétés du Task avec l'UI
label.textProperty().bind(task.messageProperty());
JLC - 4PRJ1D

Design Pattern Thread pool

Principe

  • Un nombre fixe ou dynamique de threads est maintenu dans un pool.
  • Des tâches sont placées dans une file d'attente.
  • Les threads du pool prennent et exécutent les tâches disponibles.
  • Une fois une tâche terminée, le thread devient à nouveau disponible pour une nouvelle tâche
JLC - 4PRJ1D

Design Pattern Thread pool

public static void main(String[] args) {
    // Création d'un pool de 3 threads
    ExecutorService executor = 
        Executors.newFixedThreadPool(3);

    // Soumission de 5 tâches au pool
    for (int i = 1; i <= 5; i++) {
        final int taskId = i;
        executor.submit(() -> {
            System.out.println("Tâche " + taskId + 
                " exécutée par " + 
                Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException ignored) {}
        });
    }

    executor.shutdown();
}
JLC - 4PRJ1D

JDK 21+ introduction des Virtual Threads

Plus légers et scalables.

public static void main(String[] args) {
    Thread.startVirtualThread(() -> {
        System.out.println("Virtual Thread exécuté par : " + Thread.currentThread());
        try {
            Thread.sleep(1000); // Simulation d'un traitement
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Fin du Virtual Thread : " + Thread.currentThread());
    });

    System.out.println("Thread principal terminé : " + Thread.currentThread());
}
JLC - 4PRJ1D

References

Thread. JavaDoc 23. Consulté le 8 février 2025. https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/Thread.html.

ExecutorService. JavaDoc 23. Consulté le 8 février 2025. https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/util/concurrent/ExecutorService.html.

Asynchronous Programming in Java using Virtual Threads. Venkat Subramaniam. Consulté le 8 février 2025. https://www.youtube.com/watch?v=uoTyIFvckXA.

JLC - 4PRJ1D

JDBC

Design Pattern Repository

JLC - 4PRJ1D

Java Data Base Connectivity est une API qui permet d’exploiter des bases de données au travers de requêtes SQL.
Cette API permet d’écrire un code indépendant du SGBD utilisé.

JLC - 4PRJ1D

Classes essentielles de l'API

  • DriverManager : Gère les pilotes JDBC et établit des connexions à une base de données.
  • Connection : Représente une connexion active à une base de données, permettant d'exécuter des requêtes SQL.
  • Statement : Utilisé pour exécuter des requêtes SQL statiques (SELECT, INSERT, UPDATE, DELETE).
  • ResultSet: Contient les résultats d'une requête SQL SELECT et permet d'itérer sur les données retournées.
  • SqlException : Exception levée en cas d'erreur liée à l'exécution d'une requête SQL.
JLC - 4PRJ1D

Commencer par ajouter le Driver

Driver pour SQLite

<dependency>
    <groupId>org.xerial</groupId>
    <artifactId>sqlite−jdbc</artifactId>
    <version>3.49.0.0</version>
</dependency>

Driver pour MySQL

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>9.2.0</version>
</dependency>
JLC - 4PRJ1D

Écrire une requête de selection

try {
    Connection connexion = DriverManager.getConnection("jdbc:sqlite:data/sqlite/demo.db";
    Statement stmt = connexion.createStatement();

    String query = "SELECT id,firstname,lastname FROM STUDENTS";

    ResultSet result = stmt.executeQuery(query);

    while (result.next()) {
        int id = result.getInt("id");
        String firstname = result.getString("firstname");
        String lastname = result.getString("lastname");
        System.out.println("\t record : " + id + " " + firstname + " " + lastname);
    }
} catch (SQLException ex) {
    System.out.println("DEMO_SELECT_ALL | Erreur " + ex.getMessage() + " SQLState " + ex.getSQLState());
}
JLC - 4PRJ1D

Écrire une requête de mise à jour

try {
    Connection connexion = DriverManager.getConnection("jdbc:sqlite:data/sqlite/demo.db";
    Statement stmt = connexion.createStatement();

    String query = "UPDATE STUDENTS SET firstname='Patrick',lastName='Star' where id=1 ";

    int count = stmt.executeUpdate(query);
    System.out.println("\t Nombre de record modifié : " + count);
    
} catch (SQLException ex) {
    System.out.println("DEMO_UPDATE | Erreur " + ex.getMessage() + " SQLState " + ex.getSQLState());
}
JLC - 4PRJ1D

Code vulnérable ?

public void execute(String query) {
    try {
        Connection connexion = DriverManager.getConnection("jdbc:sqlite:data/sqlite/demo.db";
        Statement stmt = connexion.createStatement();

        int count = stmt.executeUpdate(query);
        System.out.println("\t Nombre de record modifié : " + count);

    } catch (SQLException ex) {
        System.out.println("DEMO_UPDATE | Erreur " + ex.getMessage() + " SQLState " + ex.getSQLState());
    }
}
JLC - 4PRJ1D

Injection SQL

  • La méthode execute accepte une requête SQL en paramètre (query) qui est ensuite exécutée directement avec Statement.
  • Statement ne protège pas contre les entrées malveillantes.
  • Un attaquant peut injecter du SQL arbitraire en passant une requête malveillante.
execute("DELETE FROM users WHERE 1=1; --");

Suppression de toutes les lignes de la table users !

JLC - 4PRJ1D

PrepareStatement

public void executeSafe(String query, String name, int id) {
    try (Connection connexion = DriverManager.getConnection("jdbc:sqlite:data/sqlite/demo.db")) {
        String sql = "UPDATE users SET name = ? WHERE id = ?";
        try (PreparedStatement pstmt = connexion.prepareStatement(sql)) {
            pstmt.setString(1, name);
            pstmt.setInt(2, id);
            int count = pstmt.executeUpdate();
            System.out.println("\t Nombre de records modifiés : " + count);
        }
    } catch (SQLException ex) {
        System.out.println("DEMO_UPDATE | Erreur " + ex.getMessage() + " SQLState " + ex.getSQLState());
    }
}
JLC - 4PRJ1D

PreparedStatement

  • Évite l'injection SQL : Les valeurs sont séparées de la requête SQL.
  • Meilleure performance : La requête est précompilée par la base de données.
  • Les mutateurs permettent de lier des valeurs dynamiques aux requêtes SQL :
    • setString(1, "Alice")
    • setInt(2, 30)
    • setDouble(3, 1500.75)
    • setNull(13, Types.INTEGER)
    • setBoolean(5, true)
JLC - 4PRJ1D

Le design pattern Repository est un modèle d’architecture utilisé pour séparer la logique d'accès aux données de la logique métier d’une application.

JLC - 4PRJ1D

L'interface Repository

public interface Repository<T, K> {
    void save(T entity);
    void deleteById(K id);
    T findById(K id);
    List<T> findAll();
}

Décomposition des génériques <T, K>

  • T : le type d'entité (ex: User, Product, etc.).
  • K : le type de l'identifiant (ex: Integer, Long, etc.).
JLC - 4PRJ1D

Implémentation intuitive avec JDBC

public class UserSqliteRepository implements Repository<User, Integer> {
    private static final String URL = "jdbc:sqlite:users.db";
    private Connection connection;

    public UserSqliteRepository() {
        this.connection = DriverManager.getConnection(URL);
    }

    @Override
    public void save(User user)  {
        String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
        PreparedStatement pstmt = connection.prepareStatement(sql));
        pstmt.setString(1, user.getName());
        pstmt.setString(2, user.getEmail());
        pstmt.executeUpdate();
    }
JLC - 4PRJ1D

Implémentations multiples possibles

public class UserSqlLiteRepository implements Repository<User, Integer> {
...
}

public class UserMySqlRepository implements Repository<User, Integer> {
...
}

public class UserFileRepository implements Repository<User, Integer> {
...
}

public class UserFirebaseRepository implements Repository<User, Integer> {
...
}

JLC - 4PRJ1D

Data Access Object et Data Transfert Object

Le pattern Repository peut être complété par deux autres concepts clés :

  • DAO (Data Access Object) : Gère l'accès aux données dans la base de données.
  • DTO (Data Transfer Object) : Transporte des données entre couches de l’application.
JLC - 4PRJ1D

Tester une application

Des tests unitaires aux tests utilisateurs

JLC - 4PRJ1D

Un test en développement logiciel est un processus permettant de vérifier qu'un programme fonctionne comme attendu en contrôlant ses différentes fonctionnalités.

JLC - 4PRJ1D

Intérêt des tests

  • Assurer la qualité du code : Vérifier que le code respecte les exigences du projet.
  • Faciliter la maintenance : Garantir que le code ne cassent pas les parties existantes.
  • Réduire le coût des corrections : En identifiant les bugs dès la phase de développement.
  • Faciliter le travail en équipe : Assurer qu'une modification d'un développeur ne perturbe pas le travail des autres.
  • Améliorer l'expérience utilisateur : En incluant les utilisateurs dans le processus de test.
JLC - 4PRJ1D

Philosophie du test

  • Le test est un processus "destructif" : Son objectif est de mettre en évidence les erreurs et faiblesses du logiciel et non de prouver qu'il est exempt de défauts.
  • Tester, c’est exécuter le programme pour y trouver des défauts : Un bon test vise à identifier des bugs et non à confirmer que tout fonctionne parfaitement.
  • Un test ne prouve pas l'absence d’erreur : Même si un test passe avec succès, cela ne garantit pas que le logiciel est sans défauts. Il ne fait que montrer que certaines erreurs n'ont pas été détectées dans un contexte donné.
  • Un test efficace est un test qui échoue : Trouver un bug signifie que le test a réussi à mettre en évidence une faiblesse du programme permettant ainsi de l'améliorer.
JLC - 4PRJ1D

Types de tests

  • Tests unitaires : Vérifient le bon fonctionnement d'une unité de code, méthode ou classe.
  • Tests d'intégration : S'assurent que plusieurs composants fonctionnent ensemble.
  • Tests fonctionnels : Valident que le logiciel répond aux exigences métier.
  • Tests de validation : Vérifient que le logiciel respecte les spécifications définies.
  • Tests de recette : Effectués par le client pour valider que le logiciel répond à ses besoins.
  • Tests d'acceptance : Confirment que le système est prêt pour une utilisation opérationnelle.
  • Tests end-to-end : Simulent un parcours utilisateur complet.
  • Tests de performance : Évaluent la rapidité et la stabilité sous différentes charges.
  • Tests de régression : Vérifient que les modifications ne génèrent pas de nouveaux bugs.
JLC - 4PRJ1D

Gérer un projet via le cycle en V

  • Définition : Le cycle en V est un modèle de gestion de projet en développement logiciel qui organise les phases du projet en deux branches :
    • Une phase descendante de spécification et conception.
    • Une phase montante de validation et tests.

JLC - 4PRJ1D

Cycle en V

  • Principes clés :

    • Chaque phase de spécification a une phase de test associée.
    • Le passage d'une étape à l'autre est strictement séquentiel.
    • La validation commence dès les premières phases en préparant les tests en parallèle de la conception.
  • Avantages :

    • Modèle structuré permettant une traçabilité claire entre exigences et tests.
    • Adapté aux projets avec des exigences bien définies.
  • Inconvénients :

    • Manque de flexibilité face aux changements.
    • Tests réalisés tardivement, ce qui peut entraîner des corrections coûteuses.
JLC - 4PRJ1D

Les tests représentent généralement 25% à 35% du temps total du projet. Dans certains projets critiques (comme en aéronautique, santé, ou finance), ce pourcentage peut être encore plus élevé.

JLC - 4PRJ1D

Les Tests unitaires avec JUnit5

JLC - 4PRJ1D

Tests unitaires avec JUnit5

Structure d'un projet maven

my-javafx-app/
│-- src/
│   ├── main/
│   │   ├── java/
│   │   │   └── be/esi/prj/
│   │   │       ├── Demo.java
│   ├── test/
│   │   ├── java/
│   │   │   └── be/esi/prj/
│   │   │       ├── DemoTest.java
│-- pom.xml

pom.xml et dépendances

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.12.0</version>
    <scope>test</scope>
</dependency>

Annotation de test

    @Test
    void testMethod() {}
JLC - 4PRJ1D

Tests unitaires et AAA

Le Test Pattern AAA est une approche standardisée pour écrire des tests unitaires de manière claire et lisible. Il se compose de trois étapes distinctes :

  1. Arrange : Initialisation des objets et des données nécessaires pour le test.
  2. Act : Exécution de la méthode ou du comportement à tester.
  3. Assert : Validation du résultat en comparant la sortie obtenue avec la sortie attendue.
@Test
void testAddition() {
    // Arrange (Préparation)
    Calculator calculator 
                = new Calculator();

    // Act (Action)
    int result = calculator.add(2, 3);

    // Assert (Vérification)
    assertEquals(5, result, 
                "2 + 3 should equal 5");
}
JLC - 4PRJ1D

Junit5 : exemple d'annotations

@BeforeEach
void setup() {
    calculator = new Calculator();
}

@Test
void testAddition() {
    int result = calculator.add(2, 3);
    assertEquals(5, result, 
                "2 + 3 should equal 5");
}

@Test
void testSubtraction() {
    int result = calculator.subtract(3, 2);
    assertEquals(1, result, 
                    "3 - 2 should equal 1");
}
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void testProductByZero(int number) {

    int result 
        = calculator.multiply(number, 0);

    assertEquals(0, result, 
        number + " * 0 should equal 0");

}
JLC - 4PRJ1D

Annotations JUnit 5 les plus utiles

  • @Test : Indique qu'une méthode est un test unitaire.
  • @BeforeEach : Exécute une méthode avant chaque test, utile pour initialiser des objets.
  • @AfterEach : Exécute une méthode après chaque test, utile pour nettoyer des ressources.
  • @BeforeAll : Exécute une méthode statique une seule fois avant tous les tests de la classe.
  • @AfterAll : Exécute une méthode statique une seule fois après tous les tests de la classe.
  • @ParameterizedTest : Permet d'exécuter un test avec plusieurs valeurs d'entrée.
  • @ValueSource : Fournit des valeurs primitives pour un test paramétré.
  • @RepeatedTest(N) : Répète un test un certain nombre de fois.
  • @ExtendWith(Extension.class) : Utilisé pour ajouter des extensions comme Mockito.
JLC - 4PRJ1D

Quelques bonnes pratiques

JLC - 4PRJ1D

Tests unitaires et design pattern SOLID

public double calculateTotal(Order order) {
    double total = 0.0;
    for (Item item : order.getItems()) {
        total += item.getPrice() * item.getQuantity();
    }

    if (order.getDiscountCode() != null) {
        if (order.getDiscountCode().equals("SALE10")) {
            total *= 0.9; // Remise de 10%
        }
        else if (order.getDiscountCode().equals("SALE20")) {
            total *= 0.8; // Remise de 20%
        }
    }

    if (total < 50) {
        total += 10.0; // Frais de livraison si moins de 50€
    }

    total += total * 0.15; // Application de la taxe de 15%

    return total;
}
  • Méthode ayant trop de responsabilités.
  • Tests unitaires difficiles à isoler.
  • Solution, introduire les méthodes :
    • double calculateItemsTotal(Order order)
    • double applyDiscount(Order order, double total)
    • double applyShippingCost(double total)
    • double calculateItemsTotal(Order order)
JLC - 4PRJ1D

Tests unitaires et design pattern builder

public class User {
    private String firstName;
    private String lastName;
    private int age;
    private String email;

    public User(String firstName, 
                String lastName, 
                int age, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.email = email;
    }
    // Getters ...
@Test
void testUserCreationWithoutBuilder() {
    User user 
        = new User( "John", 
                "Doe",  30, 
                "john.doe@example.com");

    assertEquals("John", 
                    user.getFirstName());
    assertEquals("Doe", 
                    user.getLastName());
    assertEquals(30, 
                    user.getAge());
    assertEquals("john.doe@example.com", 
                    user.getEmail());
}
JLC - 4PRJ1D

Implémenter une classe builder

public class UserBuilder {
    private String firstName;
    private String lastName;
    private int age;
    private String email;

    public UserBuilder firstName(
            String firstName) {
        this.firstName = firstName;
        return this;
    }

    public UserBuilder age(int age) {
        this.age = age;
        return this;
    }    
    public UserBuilder lastName(
                    String lastName) {
        this.lastName = lastName;
        return this;
    }

    public UserBuilder email(
                    String email) {
        this.email = email;
        return this;
    }

    public User build() {
        return new User(firstName, 
                        lastName, age, 
                        email);
    }
}
JLC - 4PRJ1D

Le design pattern builder

@Test
void testUserCreationWithBuilder() {
    User user 
        = new UserBuilder().age(30)
            .firstName("John")
            .lastName("Doe")
            .email("john.doe@example.com")
            .build();

    assertEquals("John", 
                    user.getFirstName());
    assertEquals("Doe", 
                    user.getLastName());

    assertEquals(30, user.getAge());
    assertEquals("john.doe@example.com", 
                        user.getEmail());
}
  • Lisibilité améliorée : Construction plus claire grâce aux appels enchaînés.
  • Évite les constructeurs avec trop de paramètres : Plus besoin de se rappeler de l'ordre des paramètres d'un constructeur.
  • Facilité d'ajout de nouveaux attributs : Il suffit d'ajouter une nouvelle méthode dans UserBuilder, sans casser le code existant.
  • Meilleure réutilisation et maintenabilité des tests : On peut créer une méthode createDefaultUser() qui retourne un utilisateur préconfiguré avec le Builder.
JLC - 4PRJ1D

Un Mock est un objet simulé qui imite le comportement d'un composant réel dans un test unitaire. Il est utilisé pour remplacer des dépendances (comme une base de données, une API, un service externe) afin de tester un module de manière isolée.

JLC - 4PRJ1D

La librairie Mockito

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.15.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>5.15.2</version>
    <scope>test</scope>
</dependency>
JLC - 4PRJ1D

Nécéssité d'un Mock

Comment tester PaymentService sans tester PaymentGateway ?

public class PaymentService {
    private final PaymentGateway paymentGateway;

    public PaymentService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }

    public String processOrderPayment(Order order) {
        boolean paymentSuccess = paymentGateway.processPayment(order);
        return paymentSuccess ? "Payment Successful" : "Payment Failed";
    }
}
JLC - 4PRJ1D

Utiliser Mockito avec JUnit5

@ExtendWith(MockitoExtension.class)
class PaymentServiceTest {
    @Mock
    private PaymentGateway mockPaymentGateway; // Création du mock du PaymentGateway

    @InjectMocks
    private PaymentService paymentService; // Association du mock avec paymentService

    @Test
    void testProcessOrderPayment_successfulPayment() {
        Order order = new Order(100.0, "Laptop");
        when(mockPaymentGateway.processPayment(order)).thenReturn(true);
        String result = paymentService.processOrderPayment(order);

        assertEquals("Payment Successful", result, "Le paiement devrait être réussi");
        Mockito.verify(mockPaymentGateway, times(1)).processPayment(order);
    }
JLC - 4PRJ1D

La couverture de tests mesure le pourcentage de code exécuté lors des tests. Des outils comme JaCoCo permettent d'analyser cette couverture.

La couverture à 100% ne garantit pas un code sans erreur. Il reste important de tester des cas d'usage réels et des scénarios non triviaux pour garantir la robustesse du code.

JLC - 4PRJ1D

Couverture de tests : Tests en boite noire ou blanche

public class PasswordValidator {
    public boolean isValid(String password) {
        if (password == null || password.length() < 8) {
            return false;
        }
        boolean hasUpperCase = false, hasLowerCase = false, hasDigit = false;

        for (char c : password.toCharArray()) {
            if (Character.isUpperCase(c)) hasUpperCase = true;
            if (Character.isLowerCase(c)) hasLowerCase = true;
            if (Character.isDigit(c)) hasDigit = true;
        }

        return hasUpperCase && hasLowerCase && hasDigit;
    }
}
JLC - 4PRJ1D

Couverture de tests : Tests en boite noire

@Test
void testValidPasswords() {
    assertTrue(
        validator.isValid("SecureP@ss123"));
    assertTrue(
        validator.isValid("Hello123"));
}

@Test
void testInvalidPasswords() {
    assertFalse(validator.isValid(null));
    assertFalse(validator.isValid("short"));
    assertFalse(
        validator.isValid("alllowercase"));
    assertFalse(
        validator.isValid("ALLUPPERCASE"));
    assertFalse(
        validator.isValid("12345678"));
}

Les tests boîte noire se basent uniquement sur les entrées et sorties du programme.

La structure interne du code est ignorée.

JLC - 4PRJ1D

Couverture de tests : Tests en boite blanche

On examine le code source pour s'assurer que toutes les branches sont couvertes.

@Test
void testNullPassword() {
    assertFalse(validator.isValid(null));
}

@Test
void testShortPassword() {
    assertFalse(validator.isValid("Short1"));
}

@Test
void testOnlyLowercase() {
    assertFalse(validator.isValid("onlylowercase1"));
}
@Test
void testOnlyUppercase() {
    assertFalse(validator.isValid("ONLYUPPERCASE1"));
}

@Test
void testOnlyDigits() {
    assertFalse(validator.isValid("12345678"));
}

@Test
void testValidPassword() {
    assertTrue(validator.isValid("Valid123"));
}
JLC - 4PRJ1D

Boite noire ou boite blanche ?

  • Les tests boites noires garantissent que le programme respecte ses spécifications.
  • Les tests boite blanches s'assurent que chaque instruction a été exécutée au moins une fois.
  • Les deux types de tests sont complémentaires et doivent être utilisés ensemble pour garantir un logiciel robuste.
JLC - 4PRJ1D

Un test fonctionnel est un test qui vérifie que le logiciel répond aux exigences métier et aux spécifications fonctionnelles.

Autrement dit les tests fonctionnels vérifient que le logiciel respecte les fonctionnalités définies par les cas d'utilisation.

JLC - 4PRJ1D

Tests fonctionnels déduit d'un diagramme d'activités

  • Pour chaque cas d'utilisation on réalise un diagramme d'activité.
  • Un diagramme d'activités aide à identifier tous les chemins possibles et donc tous les cas de test.
  • Chaque transition devient un scénario de test dans un plan structuré.
  • Cette méthode garantit une couverture complète des fonctionnalités testées.
  • Idéal pour dériver un plan de test fonctionnel systématique et éviter les oublis !
JLC - 4PRJ1D

Nombre de chemins dans un diagramme d'activités

JLC - 4PRJ1D

Plan de tests fonctionnels 1/4

ID Test Scénario Étapes Données d’entrée Résultat attendu
TF-001 Connexion réussie 1. Aller sur la page de connexion
2. Entrer des identifiants valides
3. Cliquer sur "Se connecter"
Identifiant : user123
Mot de passe : password123
Redirection vers le tableau de bord
JLC - 4PRJ1D

Plan de tests fonctionnels 2/4

ID Test Scénario Étapes Données d’entrée Résultat attendu
TF-002 Tentatives infructueuses (mot de passe incorrect) 1. Aller sur la page de connexion
2. Entrer un identifiant valide et un mot de passe incorrect
3. Cliquer sur "Se connecter"
Identifiant : user123
Mot de passe : wrongpass
Message d’erreur "Mot de passe incorrect"
JLC - 4PRJ1D

Plan de tests fonctionnels 3/4

ID Test Scénario Étapes Données d’entrée Résultat attendu
TF-003 Tentatives répétées avec mot de passe incorrect (bloquage du compte) 1. Essayer 3 fois avec un mot de passe incorrect
2. Vérifier si le compte est verrouillé
Identifiant : user123
Mot de passe : wrongpass (x3)
Message "Compte bloqué"
JLC - 4PRJ1D

Plan de tests fonctionnels 4/4

ID Test Scénario Étapes Données d’entrée Résultat attendu
TF-004 Tentatives limitées (moins de 3) 1. Essayer 2 fois avec un mot de passe incorrect
2. Vérifier si le compte n'est pas bloqué
Identifiant : user123
Mot de passe : wrongpass (x2)
Affichage du message d'erreur sans blocage du compte
JLC - 4PRJ1D

Difficultés associées aux tests d'une application

JLC - 4PRJ1D

Problème du grand nombre de cas de tests

  • Complexité de gestion : Le nombre de cas de tests augmente rapidement avec la taille et la complexité de l'application, ce qui peut rendre la gestion difficile.
  • Redondance des tests : Certains tests peuvent être redondants ou couvrir des cas similaires, ce qui peut être inutile et gaspiller du temps de test.
  • Équilibre entre couverture et coût : La couverture complète des tests peut sembler idéale, mais elle peut conduire à un point de rendement décroissant, où l'effort supplémentaire pour couvrir tous les cas de tests n'apporte plus de valeur ajoutée significative.
JLC - 4PRJ1D

Conseils : Prioriser les cas de tests

  • Critères de risque : Identifiez les zones à haut risque de votre application (par exemple, des fonctionnalités critiques ou récemment modifiées).
  • Scénarios métier les plus importants : Priorisez les tests qui valident les fonctionnalités métier clés de l’application.
  • Historique des défauts : Priorisez les tests dans les zones qui ont eu des défauts fréquents ou récents.
JLC - 4PRJ1D

Conseils : Tester les cas les plus probables

  • Tests des cas courants : Concentrez-vous sur les tests qui couvrent les scénarios les plus courants ou les plus utilisés par les utilisateurs finaux.
  • Tests de régression : Concentrez-vous sur les tests qui valident que les anciennes fonctionnalités ne sont pas cassées après des modifications récentes du code.
JLC - 4PRJ1D

Conseils : Techniques de réduction des tests

  • Test basé sur l’équivalence : Regroupez les entrées similaires pour réduire le nombre de cas à tester tout en maintenant une couverture significative.
  • Tests de frontières : Testez les cas aux limites des entrées et sorties pour vérifier le bon fonctionnement du système dans les conditions extrêmes.
JLC - 4PRJ1D

Principes SOLID

&

Design patterns

JLC - 4PRJ1D

SOLID est un ensemble de principes pour améliorer la conception logicielle en Programmation Orientée Objet.

JLC - 4PRJ1D

Principe SOLID

5 principes introduits par Robert C. Martin

  • Single responsibility principle - Responsabilité unique
  • Open–closed principle - Ouvert/fermé
  • Liskov substitution principle - Substitution de Liskov
  • Interface segregation principle - Ségrégation des interfaces
  • Dependency inversion principle - Inversion des dépendances
JLC - 4PRJ1D

Single Responsibility Principle - SRP

Une classe, une fonction ou une méthode doit avoir une et une seule unique raison d'être modifiée.

Cela favorise la modularité et facilite la maintenance en évitant les classes surchargées de responsabilités.

JLC - 4PRJ1D

SRP - Exemple sur une classe

class Report {

    private String content;

    public void generate() {
        content = "Rapport de ventes\n
           ================\nTotal: 5000€\n";
    }

    public void saveToFile(String filename) {
        try (FileWriter writer 
                 = new FileWriter(filename)) {
            writer.write(content);
            System.out.println("Report saved to " 
                + filename);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

La classe a deux responsabilités :

  1. Générer le rapport (logique métier)
  2. Sauvegarder le fichier (interaction avec le système de fichiers)
JLC - 4PRJ1D

SRP - Exemple sur une classe

public class FileSaver {
    public void saveToFile(String data,
                          String filename) {
        try (FileWriter writer 
               = new FileWriter(filename)) {
            writer.write(data);
            System.out.println(
                "Report saved to " 
                + filename);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class Report {
    private String content;

    public void generate() {
        content = "Rapport de ventes\n
            ================\nTotal: 5000€\n";
    }

    public String getContent() {
        return content;
    }
}
  • Report génère le rapport (logique métier)
  • FileSaver sauvegarde le fichier (interaction avec le système de fichiers)
JLC - 4PRJ1D

SRP en lien avec le pattern Facade

public class ReportFacade {
    private Report report;
    private FileSaver saver;

    public ReportFacade() {
        this.report = new Report();
        this.saver = new FileSaver();
    }

    public void generateAndSaveReport(
            String filename) {

        report.generate();
        saver.saveToFile(
            report.getContent(),
                        filename); 
    }
}

Le design pattern Facade fournit une interface simplifiée et unifiée pour cacher la complexité d'un sous-système, en déléguant les appels aux classes internes sans les exposer directement.

ReportFacade coordonne les deux services.

JLC - 4PRJ1D

SRP - Exemple sur une méthode

Deux responsabilités par méthode

void displayWinner(List<Player> players, 
         Player currentPlayer) {

    Player winner = currentPlayer;

    for (Player player : players) {
        if(player.getScore()> 100){
            winner = player;
        }
    }

    System.out.println("Vainqueur : " 
        + winner.getName());
}

Une responsabilité par méthode

Player getWinner(List<Player> players, 
         Player currentPlayer) {
    Player winner = currentPlayer;
    for (Player player : players) {
        if (player.getScore() > 100) {
            winner = player;
        }
    }
    return winner;
}

void displayWinner(Player player){
    System.out.println("Vainqueur : " 
        + getWinner().getName());
}
JLC - 4PRJ1D

Open/Closed Principle

Une classe (ou une fonction, un module ...) doit être fermée à la modification directe mais ouverte à l'extension.

L'objectif est de permettre l'ajout de nouvelles fonctionnalités sans altérer le code existant.

JLC - 4PRJ1D

Open/Closed Principle - Exemple

public class PaymentProcessor {

    public void processPayment(String type) {

        if (type.equals("credit_card")) {

            System.out.println(
               "Processing card payment...");

        } else if (type.equals("payconiq")) {

            System.out.println(
               "Processing Payconiq payment...");
               
        }
    }
} 

Pour ajouter le Bitcoin comme moyen de paiement, il faut modifier PaymentProcessor.

Si une classe a été testée et validé, on ne la modifie pas.

JLC - 4PRJ1D

Open/Closed Principle en lien avec le pattern Strategy

interface PaymentStrategy {
    void pay();
}

class CreditCardPayment implements PaymentStrategy {
    public void pay() {

        System.out.println(
            "Processing credit card payment...");

    }
}

class PayconiqPayment implements PaymentStrategy {
    public void pay() {

        System.out.println(
            "Processing Payconiq payment...");

    }
}

Pour ajouter un nouveau moyen de paiement il faut créer une nouvelle implémentation de PaymentStrategy.

Le design pattern Strategy permet de définir une famille d'algorithmes, de les encapsuler et de les interchanger dynamiquement sans modifier le code du client, favorisant ainsi l'extensibilité.

JLC - 4PRJ1D

Open/Closed Principle en lien avec le pattern Strategy

public class PaymentProcessor {
    private PaymentStrategy strategy;

    public PaymentProcessor(
             PaymentStrategy strategy) {

        this.strategy = strategy;
    }

    public void processPayment() {
        strategy.pay();
    }

    public void setStrategy(
            PaymentStrategy strategy) {
        this.strategy = strategy;
    }
}
public static void main(String[] args) {

    CreditCardPayment cardStrategy
            = new CreditCardPayment();

    PaymentProcessor processor
            = new PaymentProcessor(
                cardStrategy);

    processor.processPayment();

    PayconiqPayment payconiqStrategy
            = new PayconiqPayment();

    processor.setStrategy(payconiqStrategy);

    processor.processPayment();
}
JLC - 4PRJ1D

Liskov Substitution Principle

Une instance de type T doit pouvoir être remplacée par une instance de type G, tel que G sous-type de T, sans que cela ne modifie la cohérence du programme.
Cela garantit que les sous-classes peuvent être utilisées de manière interchangeable avec leurs classes de base.

L'extension d'une classe ne doit pas modifier le comportement attendu des méthodes remplacées.

JLC - 4PRJ1D

Liskov Substitution Principle - Exemple

class Rectangle {
    protected int width, height;

    public Rectangle(int width, 
                int height) {
        this.width = width;
        this.height = height;
    }

    public void setWidth(int width) { 
        this.width = width; 
    }
    public void setHeight(int height) { 
        this.height = height; 
    }
    public int getArea() { 
        return width * height; 
    }
}
class Square extends Rectangle {
    public Square(int side) {
        this.width = side;
        this.height = side;
    }

    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width;
    }

    @Override
    public void setHeight(int height) {
        this.width = height;
        this.height = height;
    }
}
JLC - 4PRJ1D

Liskov Substitution Principle - Exemple

public static void main(String[] args) {
    Rectangle rect = new Rectangle();
    rect.setWidth(4);
    rect.setHeight(5);
    System.out.println("Rectangle area: " + rect.getArea());  // 4 * 5 = 20

    Rectangle square = new Square();
    square.setWidth(4);
    square.setHeight(5);

    System.out.println("Square area: " + square.getArea());  // 5 * 5 = 25 au lieu de 4 * 5
}
JLC - 4PRJ1D

Liskov Substitution Principle - Exemple

interface Shape {
    int getArea();
}

class Rectangle implements Shape {
    protected int width, height;

    public Rectangle(int width, 
               int height) {

        this.width = width;
        this.height = height;

    }
...
}

class Square implements Shape {
    private int side;

    public Square(int side) { 
        this.side = side; 
    }
    
    ...
JLC - 4PRJ1D

Liskov Substitution en lien avec le pattern factory

class ShapeFactory {

    public static Shape createRectangle(
              int width, int height) {
        return new Rectangle(width, height);
    }

    public static Shape createSquare(
               int side) {
        return new Square(side);
    }

}

La fabrique (factory) est un patron de conception créationnel utilisé en programmation orientée objet.

Elle permet d'instancier des objets dont le type est dérivé d'un type abstrait.

La classe exacte de l'objet n'est donc pas connue par l'appelant.

JLC - 4PRJ1D

Interface Segregation Principle

Préférer plusieurs interfaces spécifiques pour chaque client plutôt qu'une seule interface générale.

Cela évite aux classes de dépendre de méthodes dont elles n'ont pas besoin, réduisant ainsi les couplages inutiles.

JLC - 4PRJ1D

Interface Segregation Principle - Exemple

interface Worker {
    void work();
    void eat();
}

class Developer implements Worker {

    public void work() {
        System.out.println("Writing code...");
    }

    public void eat() {
        throw new UnsupportedOperationException("Developers don’t eat at work!");
    }
}

La classe Developer ne devrait pas être obligée d’implémenter eat().

JLC - 4PRJ1D

Interface Segregation Principle - Exemple

interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class Developer 
         implements Workable {
    public void work() {
        System.out.println(
            "Writing code...");
    }
}
class HumanWorker 
        implements Workable,
                      Eatable {

    public void work() {
        System.out.println(
            "Working...");
    }

    public void eat() {
        System.out.println(
            "Eating...");
    }
}
JLC - 4PRJ1D

Dependency Inversion Principle

Il faut dépendre des abstractions, pas des implémentations.

Cela favorise la modularité, la flexibilité et la réutilisabilité en réduisant les dépendances directes entre les modules.

JLC - 4PRJ1D

Dependency Inversion Principle - Exemple

class Keyboard {
    public String getInput() {
        return "User input";
    }
}

class Computer {
    private Keyboard keyboard = new Keyboard();

    public void useKeyboard() {
        System.out.println("Using: " 
            + keyboard.getInput());
    }
}
interface InputDevice {
    String getInput();
}

class Keyboard implements InputDevice {
    public String getInput() {
        return "User input from Keyboard";
    }
}

class Computer {
    private InputDevice inputDevice;

    public Computer(InputDevice inputDevice) {
        this.inputDevice = inputDevice;
    }

    public void useInputDevice() {
        System.out.println("Using: " 
            + inputDevice.getInput());
    }
}
JLC - 4PRJ1D

Pourquoi appliquer SOLID ?

  • Facilite la maintenance et l’évolutivité
  • Réduit le couplage et améliore la réutilisabilité
  • Facilite les tests unitaires
  • Favorise un code plus clair et modulaire
  • Conserver la conformité aux spécifications inchangées
JLC - 4PRJ1D

Principle of Least Knowledge - Loi de Demeter

La Loi de Déméter stipule qu'un module ou une classe ne doit interagir qu'avec ses dépendances directes et éviter les chaînes d’appels profondes.

Une méthode d'un objet ne doit appeler que :

  • Ses propres méthodes
  • Les méthodes de ses attributs directs
  • Les méthodes des paramètres qu’elle reçoit
  • Les méthodes de ses propres objets créés
JLC - 4PRJ1D

Loi de Demeter - Exemple

class Address {
    private String city;

    public Address(String city) {
        this.city = city;
    }

    public String getCity() {
        return city;
    }
}
JLC - 4PRJ1D

Loi de Demeter - Exemple

Order dépend de Person et de Address, le couplage est fort.

class Person {
    private Address address;

    public Person(Address address) {
        this.address = address;
    }

    public Address getAddress() {
        return address;
    }
}
class Order {
    private Person customer;

    public Order(Person customer) {
        this.customer = customer;
    }

    public void printCustomerCity() {
        System.out.println(
             customer
                  .getAddress()
                         .getCity()); 
    }
}
JLC - 4PRJ1D

Loi de Demeter - Exemple

Order ne dépend que de Person, ce qui réduit le couplage.

class Person {
    private Address address;

    public Person(Address address) {
        this.address = address;
    }

    public String getCity() { 
        return address.getCity();
    }
}
class Order {
    private Person customer;

    public Order(Person customer) {
        this.customer = customer;
    }

    public void printCustomerCity() {
        System.out.println(
            customer.getCity());
    }
}
JLC - 4PRJ1D

Limites des principes SOLID

Complexité accrue

  • Problème : Appliquer strictement SOLID peut fragmenter le code en trop de petites classes et interfaces, rendant le projet difficile à suivre.
  • Solution : Appliquer SOLID avec bon sens, en évitant une abstraction excessive si elle n’apporte pas de valeur.

Exemple :

  • Avoir une interface par type de paiement (CreditCardPayment, PaypalPayment, etc.) est utile.
  • Mais créer une interface pour chaque méthode (ProcessPayment, VerifyPayment, RefundPayment) peut être exagéré.
JLC - 4PRJ1D

Limites des principes SOLID

Risque de surconception (Overengineering)

  • Problème : Trop appliquer OCP (Ouvert/Fermé) et DIP (Dépendance Inversion) peut mener à des couches d’abstraction inutiles.
  • Solution : Appliquer KISS (Keep It Simple, Stupid) et introduire des abstractions seulement si nécessaire.

Exemple :

  • Si une classe concrète suffit (FileLogger), inutile de créer une interface (ILogger) et plusieurs implémentations si le besoin ne se présente pas encore.
JLC - 4PRJ1D

Limites des principes SOLID

Difficulté d’application dans les petits projets

  • Problème : Dans un projet simple, appliquer SOLID peut être inutilement contraignant et ralentir le développement.
  • Solution : SOLID est plus adapté aux projets évolutifs. Dans un petit projet, mieux vaut prioriser la lisibilité et la simplicité.

Exemple :

  • Une simple classe utilitaire (MathUtils) n’a pas besoin d’être fragmentée en plusieurs interfaces et classes.
JLC - 4PRJ1D

Limites des principes SOLID

Conclusion : SOLID ≠ dogme absolu

SOLID améliore la maintenabilité et la modularité d’un projet.
Mais trop l’appliquer peut compliquer inutilement le code.

Meilleure approche :

  • Trouver un équilibre entre SOLID, lisibilité et simplicité.
  • Toujours se demander : "Est-ce que cette abstraction ajoute vraiment de la valeur ?"

SOLID est un outil, pas une règle absolue !

JLC - 4PRJ1D

Retour sur l'Observateur-Observé

JLC - 4PRJ1D

Principe et implémentation

  • Principe du pattern Observateur-Observé : permet à un objet observé de notifier automatiquement ses observateurs lorsqu'un changement d'état se produit.

  • Acteurs principaux :

    • Observé (Subject) : détient l’état et notifie les observateurs en cas de modification.
    • Observateurs (Observers) : s’abonnent à l’observé et réagissent aux notifications.
JLC - 4PRJ1D

Principe et implémentation

  • Fonctionnement :

    1. Un observateur s’enregistre auprès de l’observé.
    2. L’observé détecte un changement et notifie tous ses observateurs.
    3. Chaque observateur met à jour son état en fonction des informations reçues.
  • Approches de notification :

    • Push : l’observé envoie directement les nouvelles données aux observateurs.
    • Pull : l’observé informe juste du changement, et les observateurs viennent récupérer les données.
JLC - 4PRJ1D

Principe et implémentation

  • Avantages :

    • Découplage entre l’émetteur et les récepteurs.
    • Facilite la mise à jour dynamique des composants.
  • Exemples d'utilisation :

    • Mise à jour automatique des interfaces graphiques (ex. JavaFX, Swing).
    • Communication entre modules dans une architecture logicielle (ex. MVC).
    • Gestion d'événements dans les systèmes réactifs.
JLC - 4PRJ1D

Implémentation standard : java.util

  • Observable (classe à observer) :

    • Permet de gérer une liste d'observateurs.
    • Offre des méthodes pour :
      • Ajouter (addObserver(Observer o)) et supprimer (deleteObserver(Observer o)) des observateurs.
      • Notifier les observateurs (notifyObservers() ou notifyObservers(Object arg)).
      • Marquer un changement avant la notification (setChanged()).
  • Observer (interface des observateurs) :

    • Définit une seule méthode :
      • update(Observable o, Object arg), appelée lorsqu'un changement est détecté.
JLC - 4PRJ1D

Implémentation standard est dépréciée

Une classe ou une méthode est marquée @Deprecated pour plusieurs raisons :

  • Problèmes de sécurité : L'élément peut être vulnérable (ex: Thread.stop() qui cause des incohérences).
  • Performance insuffisante : Une meilleure solution existe (ex: StringBuffer remplacé par StringBuilder).
  • Code obsolète : L’élément ne correspond plus aux bonnes pratiques actuelles.
  • Évolution de l’API : Un remplacement plus moderne est disponible.
JLC - 4PRJ1D

Implémentation standard est dépréciée

@Deprecated(since="9")
public class Observable {

This class and the Observer interface have been deprecated. The event model supported by Observer and Observable is quite limited, the order of notifications delivered by Observable is unspecified, and state changes are not in one-for-one correspondence with notifications. For a richer event model, consider using the java.beans package.

JLC - 4PRJ1D

Implémentation standard est dépréciée

  • Limitations et obsolescence :
    • Depuis Java 9, Observable et Observer sont dépréciés car :
      • Trop rigides et difficiles à étendre.
      • Peu compatibles avec la programmation réactive.
    • Alternatives recommandées :
      • PropertyChangeListener / PropertyChangeSupport pour la gestion fine des événements.
JLC - 4PRJ1D

Implémentation via java.beans

🔹 PropertyChangeListener (interface d'écouteur)

  • Interface fonctionnelle définissant une seule méthode :
    • void propertyChange(PropertyChangeEvent evt): appelée lorsqu'une propriété change.

🔹 PropertyChangeSupport (mécanisme de gestion d'événements)

  • Classe facilitant la gestion des écouteurs (PropertyChangeListener).
  • Offre des méthodes pour :
    • Ajouter un écouteur : addPropertyChangeListener(PropertyChangeListener listener).
    • Supprimer un écouteur : removePropertyChangeListener(PropertyChangeListener listener).
    • Notifier un changement de propriété : firePropertyChange(String propertyName, Object oldValue, Object newValue).
JLC - 4PRJ1D

JavaFX et Property

  • Les Property en JavaFX permettent de gérer les changements d’état des objets et facilitent le Binding.

🔹 Types de Property en JavaFX

  • JavaFX fournit plusieurs types de Property pour différents types de données :
    • IntegerProperty, DoubleProperty, BooleanProperty, StringProperty pour les types primitifs.
    • ObjectProperty<T> pour les objets.
JLC - 4PRJ1D

JavaFX et Property

🔹 Exemple d'utilisation

IntegerProperty compteur = new SimpleIntegerProperty(0);
compteur.addListener((obs, oldVal, newVal) -> 
    System.out.println("Nouvelle valeur : " + newVal));
compteur.set(10); // Affiche : Nouvelle valeur : 10
JLC - 4PRJ1D

JavaFX et Property

🔹 Fonctionnalités principales

  • Stocke une valeur comme un attribut classique.
  • Est observable : permet d’écouter les changements de valeur.
  • Peut être liée (Binding) à d'autres propriétés pour mettre à jour automatiquement les valeurs.
JLC - 4PRJ1D

JavaFX et Property

🔹 Méthodes clés

  • get() : retourne la valeur de la propriété.
  • set(newValue) : modifie la valeur et notifie les observateurs.
  • addListener(ChangeListener<? super T> listener) : écoute les changements de valeur.
  • bind(ObservableValue<? extends T> other) : lie la propriété à une autre (valeur mise à jour automatiquement).
  • unbind() : supprime la liaison.
JLC - 4PRJ1D

Architecture et gestion de projet

MVC et ses variantes

JLC - 4PRJ1D

En informatique, un patron d’architecture est une solution générale et réutilisable à un problème d’architecture récurrent. Les patrons d’architecture sont semblables aux patrons de conception mais ont une portée plus large. Ils servent de modèle de référence et de source d’inspiration lors de la conception de l’architecture d’un système ou d’un logiciel informatique, pour décomposer celui-ci en éléments plus simples.

JLC - 4PRJ1D

Illustrer une architecture via un exemple simplifié

  • L'application implémentée est simple pour mieux comprendre les différences entre les architectures.
  • Elle génère un nombre entier aléatoire compris entre 0 et 50.
JLC - 4PRJ1D

Exemple simplifié : la vue

La vue est composée de trois éléments :

  • un bouton à usage unique
  • un cercle de couleur
  • un nombre entier

La valeur et la couleur affichée change après une pression sur le bouton de l’interface.

JLC - 4PRJ1D

Exemple simplifié : Main dépend de JavaFX

import javafx.application.Application;
import javafx.stage.Stage;

public class Main extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        ...
        stage.setScene(scene);
        stage.show();
    }
JLC - 4PRJ1D

Exemple simplifié : View encapsule JavaFX

  public View() {
        Platform.startup(() -> {
            Stage stage = new Stage();
            ...
            stage.setScene(scene);
            stage.show();
JLC - 4PRJ1D

Exemple simplifié : le modèle

Le modèle de l’application est composé d’un nombre entier qui prend la valeur 42 lors de l’initialisation du modèle. Une méthode compute permet de demander au modèle de générer un nombre aléatoire entre 0 et 50 pour remplacer la valeur initiale.

JLC - 4PRJ1D

Exemple simplifié : Date Tranfert Object

Le transfert des données est effectué dans les différentes architectures par des DTO. Afin de garder le logiciel le plus simple possible, nous utiliserons des entiers et des String pour jouer le rôle de DTO.

JLC - 4PRJ1D

Exemple simplifié : Repository

JLC - 4PRJ1D

Architecture de base : 3-tiers

Composants :

  • Couche présentation : gère l’affichage des
    données pour l’utilisateur et capte les inter-
    actions avec celui-ci
  • Couche métier : traite les données de l’ap-
    plication, cette couche contient la logique de
    l’application
  • Couche d’accès aux données : gère l’accès
    et la persistance des données

JLC - 4PRJ1D

Architecture 3-tiers : Diagramme de classes

  • La vue dépend du Modèle.
  • La vue donne des ordres au Modèle.
  • Lorsqu’un utilisateur clique sur un bouton de l’interface, la couche présentation appelle une méthode de la couche métier.
  • La couche métier, si elle a besoin d’accéder à des données persistées, appelle des méthodes de la couche d’accès aux données.
  • Chaque couche communique avec la couche inférieure via des méthodes échangeant des DTO (Data Transfer Object).
JLC - 4PRJ1D

Architecture 3-tiers

  • Grâce à une définition claire des rôles et des interfaces de communication, chaque couche peut évoluer sans impacter les autres.
  • La couche d’accès aux données peut être implémentée avec le patron Repository, qui utilise des DTOs pour la communication.
  • Dans l’implémentation du projet ThreeLayers, la vue est active : elle donne directement des ordres au modèle logique.
JLC - 4PRJ1D

Modèle-vue-contrôleur

Composants :

  • Le modèle : contient la logique métier, elle traite les données de l’application
  • Les vues : présentation visuelle de l’état du modèle
  • Les contrôleurs : déclencheurs d’actions à effectuer sur le modèle et/ou sur une vue

JLC - 4PRJ1D

MVC : Diagramme de classes

JLC - 4PRJ1D

MVC : couche accès au données

  • Le patron MVC ne mentionne pas explicitement l'accès aux données :
    • En réalité, cette responsabilité est intégrée dans le modèle.
    • Le modèle peut accéder aux données via un Repository.
    • Par souci de simplicité, ce Repository sera omis dans la suite.
JLC - 4PRJ1D

MVC

  • Ce découpage permet d’isoler les différentes parties du logiciel, limitant ainsi l'impact des modifications sur les autres parties.
  • Dans une architecture trois-tiers, la gestion de plusieurs vues peut devenir complexe.
    • Cette complexité vient du fait que la vue est active et dirige le modèle.
    • Lorsqu'une vue met à jour une donnée, elle doit ensuite informer toutes les autres vues, ce qui peut devenir contraignant.
  • Pour résoudre ce problème, le MVC utilise le patron Observateur-Observé :
    • Lorsque le modèle met à jour une donnée, il notifie automatiquement toutes les vues.
JLC - 4PRJ1D

MVC : Dépendances

  • La vue dépend faiblement du modèle via l’Observateur-Observé
  • Le contrôleur est fortement dépendant de la vue et du modèle
  • La vue met à la disposition du contrôleur des méthodes pour mettre à jour l’affichage (disableBouton, setColorYellow, setNumber)
  • Le contrôleur est responsable de lier le modèle à la vue via la méthode addObserver
JLC - 4PRJ1D

MVC : Tests unitaires

  • Le Modèle contient la logique métier et doit être testé unitairement ;
  • Le Contrôleur contient les algorithmes qui interprètent les actions de l’utilisateur et
    doit être testé unitairement. Il faut recourir au Mock de la Vue et du Modèle
  • La méthode update de la Vue contient la logique de mise à jour et doit être testée unitairement. Ce qui n’est malheureusement pas possible de manière simple
JLC - 4PRJ1D

MVC : Popularité

Le patron d’architecture MVC est assez populaire. On peut notamment le rencontrer dans des applications développées via les frameworks Ruby on Rails, Spring, Struts, Symfony, Laravel, ou AngularJs

JLC - 4PRJ1D

Modèle-Vue-Présentation

  • Le Modèle : contient la logique métier, elle
    traite les données de l’application
  • Les Vues : présentation visuelle de l’état du
    modèle
  • La Présentation : déclencheurs d’actions à
    effectuer sur le modèle et/ou sur une vue

JLC - 4PRJ1D

MVP : Diagramme de classes

JLC - 4PRJ1D

MVP : Dépendances

  • La Vue ne dépend pas du Modèle
  • La Présentation dépend de la Vue et du Modèle
  • La Présentation est abonnée aux notifications du Modèle via l’Observateur-Observé
  • La Vue met à disposition de la Présentation des méthodes pour mettre à jour l’affichage (disableBouton, setColorYellow, setNumber)
JLC - 4PRJ1D

MVP : bilan

  • Le MVP élimine l’interaction directe entre la Vue et le Modèle.
  • La Présentation prend en charge la gestion des interactions entre ces deux composants.
  • Dans cette architecture :
    • La Vue est passive et ne donne plus aucun ordre.
    • La Vue ne contient plus de logique.
JLC - 4PRJ1D

MVP : Tests unitaires

  • Le modèle contient la logique métier et doit être testé unitairement
  • La présentation contient les algorithmes qui interprètent les actions de l’utilisateur et doit être testé unitairement. Il faut recourir au Mock de la vue et du modèle
  • La vue ne contient plus de logique et ne doit pas être testée unitairement
JLC - 4PRJ1D

MVP : Popularité

On retrouve fréquemment l’utilisation de ce patron d’architecture dans des applications Android, .NET.

JLC - 4PRJ1D

Modèle-Vue-Vue-modèle

  • Modèle : contient la logique métier, elle traite
    les données de l’application
  • Vues : présentation visuelle de l’état du modèle
  • Vue-modèle : gère les actions de l’utilisateur et modifie l’affichage via le système de Binding. Certains de ses attributs sont des conteneurs des valeurs affichées dans les vues.

JLC - 4PRJ1D

MVVM : Binding

  • Lorsqu'une vue affiche un nombre entier avec javafx.scene.text.Text :

    • La valeur de cet entier est stockée dans un attribut du Vue-modèle.
    • Cet attribut est un Observable, et son Observateur est l'instance Text de la vue.
  • Objectif : éviter de coder manuellement les mises à jour de la vue.

    • Dès qu’un attribut du Vue-modèle est modifié, le composant associé dans la vue se met automatiquement à jour.
JLC - 4PRJ1D

MVVM : Binding

  • Cette architecture repose sur le Binding, qui est dépendant des bibliothèques utilisées pour la gestion de la vue.
    • En JavaFX, les composants standards (Button, Label, TextField...) prennent en charge le Binding facilement.
    • Problème : certains composants ne supportent pas le Binding nativement.
    • Solution : implémenter manuellement le Binding dans l'application.
JLC - 4PRJ1D

MVVM : Diagramme de classes

JLC - 4PRJ1D

MVVM : Dépendances

  • La Vue dépend faiblement du Vue-modèle via le binding
  • Le Vue-modèle ne dépend pas de la Vue
  • Le Vue-modèle dépend du Modèle
  • Le Vue-modèle possède des attributs qui reflètent la Vue : la valeur de l’entier, le style associé au cercle, la statut du bouton
  • Le Vue-modèle est abonnée aux notifications du Modèle via l’Observateur-Observé
  • Le Vue-modèle dépend de l’existence d’attributs Observable, nommé Property dans le cas de JavaFX
JLC - 4PRJ1D

MVVM : Tests unitaires

  • Le Modèle contient la logique métier et doit être testé unitairement
  • Le Vue-modèle contient les algorithmes qui interprètent les actions de l’utilisateur et doit être testé unitairement. Il faut recourir au Mock du Modèle uniquement
  • La Vue ne contient plus de logique et ne doit pas être testée unitairement
JLC - 4PRJ1D

MVVM : Popularité

Actuellement on retrouve notamment l’utilisation de ce patron d’architecture dans les applications Vue.js ou encore Android.

JLC - 4PRJ1D

Autres Architecture

  • Microservices : Architecture qui décompose une application en plusieurs services indépendants, chacun responsable d'une fonctionnalité spécifique, communiquant entre eux via des API.
  • Hexagonal : Architecture centrée sur le domaine métier, où le modèle est indépendant des interfaces utilisateur et des systèmes externes, facilitant ainsi les tests et l'évolution.
  • Client-serveur : Architecture où un serveur central fournit des ressources ou des services à des clients qui en font la demande via un réseau.
JLC - 4PRJ1D

Loi de Conway

Toute organisation qui conçoit un système, au sens large, concevra une structure qui sera la copie de la structure de communication de l’organisation.

JLC - 4PRJ1D

Choix d’une architecture

  • Lors de l'implémentation d'une application, l'architecture choisie doit tenir compte de plusieurs critères :

    • Framework adapté
    • Popularité de l'architecture dans la technologie
    • Vue active ou passive
    • Évolution de l’application : nombre de vues fixe ou variable
    • Couverture de test souhaitée
  • Il n'existe pas de classification absolue des architectures.

  • Ces questions doivent être discutées au début de chaque nouvelle application.

JLC - 4PRJ1D

Présenter une analyse

JLC - 4PRJ1D

En génie logiciel, une méthodologie est un procédé qui a pour objectif de permettre de formaliser les étapes du développement d'un système afin de rendre ce développement plus fidèle aux besoins du client.

JLC - 4PRJ1D

Méthodologie

  • Méthode en cascade (Waterfall) : Processus linéaire et séquentiel, chaque phase (analyse, conception, implémentation, test, déploiement) doit être complétée avant de passer à la suivante.

  • Méthode cyclique (itérative/incrémentale) : Développement en plusieurs cycles où chaque itération produit une version améliorée du logiciel.

  • Méthode agile : Approche flexible et collaborative privilégiant les interactions fréquentes avec le client, les livraisons incrémentales et l’adaptation continue.

JLC - 4PRJ1D

Etapes clés

  • Description des besoins : recueillir et analyser les besoins.

  • Cas d'utilisations : représenter les interactions entre les acteurs et le logiciel.

  • Maquette d'écran : Prototyper les interfaces utilisateur.

  • Plan de tests fonctionnels : Décrire les scénarios de tests permettant de valider chaque fonctionnalité.

  • Diagramme de classes : Modéliser la structure du code (classes, attributs, méthodes et relations).

  • Répartition des tâches : Organiser le travail entre les membres de l’équipe.

JLC - 4PRJ1D

Description des besoins

Objectif : Comprendre ce que doit accomplir le projet.

  • Se concentre sur ce que veut le client ou les utilisateurs.

  • Exprime les attentes sous forme de besoins fonctionnels et non fonctionnels, souvent en langage naturel.

  • Document initial permettant de cadrer le projet avant de rentrer dans les détails techniques : cahier des charges.

JLC - 4PRJ1D

Besoins fonctionnels

  • Décrit ce que le système doit faire (ses fonctionnalités).

  • Correspond aux actions et interactions entre l'utilisateur et le logiciel.

Exemples :

  • Un utilisateur doit pouvoir s’inscrire et se connecter.
  • L’application doit permettre d’ajouter un produit au panier.
JLC - 4PRJ1D

Besoins non-fonctionnels

  • Décrit comment le système doit fonctionner

  • Contraintes et qualités attendues.

  • Ne concerne pas directement une fonctionnalité, mais plutôt des critères de performance, sécurité, ergonomie, etc.

Exemples :

  • Le site doit charger en moins de 2 secondes.
  • L’application doit être accessible sur mobile et desktop.
  • Les données des utilisateurs doivent être cryptées.
JLC - 4PRJ1D

Exemple : Application de gestion des étudiants

Développer une interface permettant d'ajouter des étudiants à un tableau après saisie de leurs informations dans des champs dédiés. Les données des étudiants doivent être enregistrées dans un fichier pour assurer leur persistance. L'interface comprendra un tableau avec trois colonnes : Matricule, Nom et Prénom. Au démarrage de l'application, les étudiants seront chargés depuis le fichier. L'application devra inclure une gestion des erreurs, notamment en empêchant l'ajout d'un étudiant si un champ est vide. Enfin, elle devra permettre la suppression d'un étudiant existant.

JLC - 4PRJ1D

Spécification via des cas d'utilisation

Définir les interactions entre utilisateurs et système

Un cas d’utilisation comprend généralement :

  • Acteurs : Utilisateurs ou systèmes interagissant avec l’application.
  • Scénario principal (nominal) : Séquence d’actions entre l’acteur et le système.
  • Exceptions / Alternatives : Déviations possibles du scénario principal.
  • Conditions : pré, post, invariable.

Avantages : Clarifie les exigences fonctionnelles, facilite la communication entre équipes.

JLC - 4PRJ1D

Exemple : Application de gestion des étudiants

Le contrôleur traduit les actions utilisateur en logique applicative.

  • 3 cas d'utilisations - 3 méthodes du controlleur :

    • Chargement des étudiants au démarrage

    • Ajout d’un étudiant

    • Suppression d’un étudiant

JLC - 4PRJ1D

Maquettage des écrans

  • Conception de l’interface utilisateur avant le développement

  • Outils : Figma, croquis papier, SceneBuilder

  • Clarification des besoins

  • Communication avec les parties prenantes

  • Réduction des coûts et des risques

JLC - 4PRJ1D

Plan de Tests Fonctionnels

Raison de l'écriture du plan de tests durant la phase d'analyse :

  • Validation des exigences

  • Détection précoce des incohérences

  • Meilleure planification du projet

  • Assurance qualité dès le départ

Format du plan de test

ID Test Données d’entrée Résultat attendu
T3 Ajout d’un étudiant valide Matricule: 123, Nom: "Dupont", Prénom: "Jean" L'étudiant est ajouté au TableView et au fichier
JLC - 4PRJ1D

Diagramme de Classes

  • Structuration du modèle conceptuel

  • Identification des relations et responsabilités

  • Réduction des erreurs et meilleure planification

  • Base pour l’implémentation

  • Correspondance entre classes et tables

JLC - 4PRJ1D

Répartition des Tâches

Semaine 1 - 6H

Qui Description
Tous Analyse du projet.
Alice Initialisation de Git
Bob Configuration de l'environnement

Semaine 2 - 6H

Qui Description
Alice Intégration de la librairie
Bob Implémentation de la base de données
JLC - 4PRJ1D

Projet du bimestre 4

  • Application Java intégrant une librairie fixée par les enseignants

  • Travail en binôme

  • FAQ du projet

  • Evaluation pratique et théorique

JLC - 4PRJ1D

Formuler une demande d’aide claire

  • Titre du ticket : résumé du problème

  • Description détaillée du problème : résultat attendu vs résultat réel

  • Étapes pour reproduire le bug

  • Copie du message d’erreur : Caused by

  • Environnement technique

  • Tentatives de correction effectuées

  • Priorité (Critique, Majeu, Mineur, Amélioration)

  • Outil comme Jira dans le milieu professionnel

JLC - 4PRJ1D

Exemple de ticket - Part 1

Titre : Erreur IllegalStateException lors du chargement du fichier FXML

Description :
Lors du lancement de l’application via la commande maven, une exception IllegalStateException est levée lorsque j’essaie de charger le fichier student-view.fxml.
L’interface graphique ne s’affiche pas comme prévu.

Étapes pour reproduire :

  1. Exécuter l’application via mvn javafx:run
  2. Vérifier la console pour voir le message d’erreur
JLC - 4PRJ1D

Exemple de ticket - Part 2

Message d’erreur :

java.lang.IllegalStateException: Location is not set. 
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2561) at 
javafx.fxml.FXMLLoader.load(FXMLLoader.java:2536) at be.esi.prj.
studentmanager.view.ViewFx.lambda$new$0(ViewFx.java:23)

Environnement :

  • OS : Windows 11
  • Java : OpenJDK 17
JLC - 4PRJ1D

Exemple de ticket - Part 3

Tentatives de correction :
J’ai essayé d’utiliser getResource("/be/esi/prj/studentmanager/view/student-view.fxml") au lieu de getResource("./student-view.fxml") à la ligne 23 de la classe ViewFx mais l’erreur persiste.

Priorité : Majeur

JLC - 4PRJ1D

THANK YOU!

JLC - 4PRJ1D

<div class="columns"> <div>

</div> <div> 1. **Thread1** acquiert **Ressource1** et dort pendant 100 ms. 2. **Thread2** acquiert **Ressource2** et dort pendant 100 ms. 3. **Thread1** essaie de récupérer **Ressource2**, mais elle est déjà occupée par **Thread2**. **Thread1** se bloque en attendant. 4. **Thread2** essaie de récupérer **Ressource1**, mais elle est déjà occupée par **Thread1**. **Thread2** se bloque également en attendant. 5. Résultat : les deux threads sont **bloqués indéfiniment**, créant une **impasse** ou **deadlock**. </div> </div>

- La maintenance devient un cauchemar.