Skip to content

9. [TD] : Implémentation de la couche [ui] avec un programme console

Mots clés : architecture multicouche, Spring, injection de dépendances.

9.1. Support

En [1], le dossier [support / chap-09] contient le projet Eclipse de la couche [UI] de l'application console.

9.2. Configuration Maven

Le projet Eclipse [elections-ui-metier-dao-jdbc] est configuré par le fichier Maven [pom.xml] suivant :


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.elections</groupId>
    <artifactId>elections-ui-metier-dao-jdbc</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elections-ui-metier-dao-jdbc</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- métier -->
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-metier-dao-jdbc</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Spring Boot Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <!-- plugins -->
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>config.AppConfig</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <!-- pour l'installation de l'artifact du projet dans le dépôt local Maven -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
</project>
  • lignes 22-26 : on importe l'archive de la couche [métier] et par cascade celle de la couche [DAO] ;

9.3. Configuration Spring

  

La classe [UiConfig] configure l'application Spring :


package elections.ui.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

import elections.metier.config.MetierConfig;

@Import(MetierConfig.class)
@ComponentScan(basePackages = { "elections.ui.service" })
public class UiConfig {
}
  • ligne 8 : on importe les beans définis dans la couche [métier]. Celle-ci importait déjà les beans définis dans la couche [DAO]. On a donc ici accès aux beans des trois couches ;
  • ligne 9 : le package [elections.ui.service] contient d'autres beans ;

9.4. L'interface de la couche [UI]

  

Pour comprendre ce que peut être l'interface Java de la couche [UI], il nous faut savoir qui va utiliser cette interface. Il ne s'agit pas de l'utilisateur du schéma ci-dessus mais du programme qui va lancer l'application dans son ensemble.

L'interface [IElectionsUI] est présentée au programme principal [main] qui va lancer l'application. Que peut demander [main] à la couche [ui] ? Elle peut lui demander de commencer les échanges avec l'utilisateur qui va saisir les données manquantes de l'élection. On adoptera l'interface minimale suivante :


package istia.st.elections.ui;

public interface IElectionsUI {
    /**
     * lance le dialogue avec l'utilisateur
     */
    public void run();
}
  • ligne 7 : l'interface n'a qu'une unique méthode : run. En appelant cette méthode, on demande à la couche [ui] de commencer les échanges avec l'utilisateur.

9.5. La classe de démarrage de l'application

  

Le classe de démarrage de l'application est une classe Java ayant une méthode statique [main]. Cette méthode doit créer des instances des couches [ui, metier, demo] et demander à la couche [ui] de commencer le dialogue avec l'utilisateur. Cette classe pourrait être la classe [AbstractBootElections] suivante :


package elections.ui.boot;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import elections.dao.entities.ElectionsException;
import elections.ui.config.UiConfig;
import elections.ui.service.IElectionsUI;

public abstract class AbstractBootElections {

    // récupération du contexte Spring
    protected AnnotationConfigApplicationContext ctx;

    public void run() {
        // instanciation couche [ui]
        IElectionsUI electionsUI = null;
        try {
            // récupération du contexte Spring
            ctx = new AnnotationConfigApplicationContext(UiConfig.class);
            // récupération de la couche [ui]
            electionsUI = getUI();
        } catch (RuntimeException ex) {
            // on signale l'erreur
            afficheExceptions("Les erreurs suivantes se sont produites :", ex);
            // on arrête l'application
            System.exit(1);
        }
        // exécution couche [ui]
        try {
            electionsUI.run();
        } catch (ElectionsException ex2) {
            // on signale l'erreur
            afficheExceptions("Les erreurs suivantes se sont produites :", ex2);
            // on arrête l'application
            System.exit(3);
        } catch (RuntimeException ex1) {
            // on signale l'erreur
            afficheExceptions("Les erreurs suivantes se sont produites :", ex1);
            // on arrête l'application
            System.exit(2);
        }
    }

    protected abstract IElectionsUI getUI();

    private void afficheExceptions(String message, ElectionsException ex) {
        // on affiche le message
        System.out.println(String.format("%s -------------", message));
        System.out.println(String.format("Code erreur : %d", ex.getCode()));
        // on affiche les erreurs
        for (String erreur : ex.getErreurs()) {
            System.out.println(String.format("-- %s", erreur));
        }

    }

    public void afficheExceptions(String message, Exception ex) {
        // on affiche le message
        System.out.println(String.format("%s -------------", message));
        // on affiche la pile des exceptions
        Throwable cause = ex;
        while (cause != null) {
            System.out.println(String.format("-- %s", cause.getMessage()));
            cause = cause.getCause();
        }
    }
}
  • ligne 19 : le contexte Spring est instancié : tous les beans définis dans les différents fichiers de configuration vont être créés ;
  • ligne 21 : on récupère une référence sur le bean qui implémente l'interface [IElectionsUI]. Nous allons implémenter l'interface [IElectionsUI] par deux beans :
    • [ElectionsConsole] pour une application console ;
    • [ElectionsSwing] pour une application Swing ;

La méthode [getUI] est abstraite (ligne 44). En effet, la classe [AbstractBootElections] va être parente de deux classes :

  • [BootElectionsConsole] qui fournira le bean [ElectionsConsole] à sa classe parent ;
  • [BootElectionsSwing] qui fournira le bean [ElectionsSwing] à sa classe parent ;
  • ligne 30 : la méthode [run] de l'interface est exécutée ;

Les exceptions sont gérées à divers endroits :

  • lignes 22-27 : une exception peut se produire lors de l'instanciation du contexte Spring ;
  • lignes 31-41 : une exception peut se produire lors de l'exécution de la couche [UI]. On distingue deux types d'exception :
    • la classe [ElectionsException] que la couche [DAO] lance ;
    • la classe [RuntimeException] pour les autres exceptions qui pourraient survenir pendant l'exécution ;

La classe [BootElectionsConsole] est la classe qui lance l'implémentation console. Son code est le suivant :


package elections.ui.boot;

import elections.ui.service.IElectionsUI;

public class BootElectionsConsole extends AbstractBootElections{
    public static void main(String[] arguments) {
        new BootElectionsConsole().run();
    }

    @Override
    protected IElectionsUI getUI() {
        return ctx.getBean("electionsConsole",IElectionsUI.class);
    }
}
  • ligne 5, la classe [BootElectionsConsole] étend la classe [AbstractBootElections]. Elle doit donc implémenter la méthode [getUI] que sa classe parente avait déclarée abstraite ;
  • lignes 10-13 : implémentation de la méthode [getUI] ;
  • ligne 12 : on rend le bean nommé [electionsConsole] implémentant l'interface [IElectionsUI]. [ctx] est le contexte Spring défini dans la classe parente par la déclaration :

    // récupération du contexte Spring
    protected AnnotationConfigApplicationContext ctx;

Parce que le champ a l'attribut [protected], il est visible dans les classes filles.

Le bean de la ligne 12 sera déclaré de la façon suivante :


@Component
public class ElectionsConsole implements IElectionsUI {

Le nom par défaut de ce bean est le nom de la classe avec sa première lettre en minuscule. On peut s'affranchir de ce nom par défaut en écrivant explicitement :

@Component(nom_du_bean_entre_guillemets)

La classe [BootElectionsSwing] qui lancerait l'implémentation Swing pourrait être la suivante :


package elections.ui.boot;

import elections.ui.service.IElectionsUI;

public class BootElectionsSwing extends AbstractBootElections {
    public static void main(String[] arguments) {
        new BootElectionsSwing().run();
    }

    @Override
    protected IElectionsUI getUI() {
        return ctx.getBean("electionsSwing", IElectionsUI.class);
    }
}

Ce modèle de conception où on factorise le comportement commun à des classes dans une classe parent, et où on laisse les classes filles implémenter les détails qui leur sont spécifiques s'appelle le modèle de conception Strategy. Ce modèle de conception impose un comportement commun à toutes les classes filles.

9.6. La classe d'implémentation [ElectionsConsole]

  

Notre première classe d'implémentation de la couche [ui] sera une classe utilisant la console pour communiquer avec l'utilisateur. Voici un exemple de dialogue obtenu sur la console Eclipse :


Il y a 7 listes en compétition. Veuillez indiquer le nombre de voix de chacune d'elles :
Nombre de voix de la liste [A] : 2500
Nombre de voix de la liste [B] : 4500
Nombre de voix de la liste [C] : x
Nombre de voix incorrect. Veuillez recommencer
Nombre de voix de la liste [C] : 8000
Nombre de voix de la liste [D] : 12000
Nombre de voix de la liste [E] : 16000
Nombre de voix de la liste [F] : 25000
Nombre de voix de la liste [G] : 32000

Résultats de l'élection

[G,32000,2,false]
[F,25000,2,false]
[E,16000,1,false]
[D,12000,1,false]
[C,8000,0,false]
[B,4500,0,true]
[A,2500,0,true]

La classe [ElectionsConsole] pourrait avoir le squelette suivant :


package elections.ui.service;

import java.util.Comparator;
import java.util.Scanner;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import elections.dao.entities.ListeElectorale;
import elections.metier.service.IElectionsMetier;

@Component
public class ElectionsConsole implements IElectionsUI {

    @Autowired
    private IElectionsMetier electionsMetier;

    @Override
    public void run() {
        // saisie des données
        try (Scanner clavier = new Scanner(System.in)) {
            // on demande les listes en compétition à la couche [metier]

            // on fait la saisie des voix

        }
        // on fait le calcul des sièges

        // on enregistre les résultats

        // tri des listes dans l'ordre décroissant des voix

        // on les affiche
    }

    // classe de comparaison de listes électorales
    class CompareListesElectorales implements Comparator<ListeElectorale> {

        // comparaison de deux listes candidates selon le nombre de voix
        @Override
        public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
            // on compare les voix de ces deux listes
            int nbVoix1 = listeElectorale1.getVoix();
            int nbVoix2 = listeElectorale2.getVoix();
            if (nbVoix1 < nbVoix2) {
                return +1;
            } else {
                if (nbVoix1 > nbVoix2)
                    return -1;
                else
                    return 0;
            }
        }
    }

}
  • ligne 12 : la classe [ElectionsConsole] est un composant Spring ;
  • ligne 13 : la classe implémente l'interface [IElectionsUI]
  • lignes 15-16 : injection par Spring d'une référence sur la couche [metier] ;
  • ligne 18-34 : la méthode [run] de l'interface [IelectionsUI] ;

Le try des lignes 21-28 s'appelle try-with-resources et sa syntaxe est la suivante :

1
2
3
try(ressource){
}

La ressource de la ligne 1 doit implémenter l'interface [java.lang.AutoCloseable]. La ressource est ouverte ligne1 et automatiquement fermée après la ligne 3, qu'il y ait exception ou non dans le code exécuté entre les deux lignes. Cette syntaxe permet de s'assurer qu'une ressource ouverte sera bien fermée, quoiqu'il arrive.


Travail à faire : écrivez le code de la méthode [run]. On s'aidera des commentaires.