Skip to content

9. [TD]: Implementazione del livello [ui] con un programma da console

Parole chiave: architettura multistrato, Spring, iniezione di dipendenze.

9.1. Assistenza

In [1], la cartella [support / chap-09] contiene il progetto Eclipse per il livello [UI] dell'applicazione console.

9.2. Configurazione Maven

Il progetto Eclipse [elections-ui-metier-dao-jdbc] è configurato dal seguente file Maven [pom.xml]:


<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>
        <!-- business -->
        <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>
            <!-- to install the project artifact in the local Maven repository -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
</project>
  • righe 22–26: importiamo l'archivio del livello [business] e, per estensione, l'archivio del livello [DAO];

9.3. Configurazione Spring

  

La classe [UiConfig] configura l'applicazione 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 {
}
  • Riga 8: importiamo i bean definiti nel livello [business]. Questo livello ha già importato i bean definiti nel livello [DAO]. Abbiamo quindi accesso qui ai bean di tutti e tre i livelli;
  • riga 9: il pacchetto [elections.ui.service] contiene altri bean;

9.4. L'interfaccia del livello [UI]

  

Per capire come potrebbe essere l'interfaccia Java del livello [UI], dobbiamo sapere chi utilizzerà questa interfaccia. Non si tratta dell'utente del diagramma sopra riportato, ma del programma che avvierà l'applicazione nel suo complesso.

L'interfaccia [IElectionsUI] viene presentata al programma principale [main], che avvierà l'applicazione. Cosa può chiedere [main] al livello [UI]? Può chiedergli di avviare interazioni con l'utente che inserirà i dati elettorali mancanti. Adotteremo la seguente interfaccia minimale:


package istia.st.elections.ui;
 
public interface IElectionsUI {
    /**
     * lance le dialogue avec l'utilisateur
     */
    public void run();
}
  • Riga 7: L'interfaccia ha un solo metodo: run. Richiamando questo metodo, si indica al livello [ui] di iniziare a interagire con l'utente.

9.5. Il punto di ingresso dell'applicazione

  

La classe di avvio dell'applicazione è una classe Java con un metodo statico [main]. Questo metodo deve creare istanze dei livelli [ui, business, demo] e istruire il livello [ui] ad avviare l'interazione con l'utente. Questa classe potrebbe essere la seguente classe [AbstractBootElections]:


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 {
 
    // spring context retrieval
    protected AnnotationConfigApplicationContext ctx;
 
    public void run() {
        // instantiation layer [ui]
        IElectionsUI electionsUI = null;
        try {
            // spring context retrieval
            ctx = new AnnotationConfigApplicationContext(UiConfig.class);
            // ui] layer recovery
            electionsUI = getUI();
        } catch (RuntimeException ex) {
            // we report the error
            afficheExceptions("Les erreurs suivantes se sont produites :", ex);
            // stop the application
            System.exit(1);
        }
        // execution layer [ui]
        try {
            electionsUI.run();
        } catch (ElectionsException ex2) {
            // we report the error
            afficheExceptions("Les erreurs suivantes se sont produites :", ex2);
            // stop the application
            System.exit(3);
        } catch (RuntimeException ex1) {
            // we report the error
            afficheExceptions("Les erreurs suivantes se sont produites :", ex1);
            // stop the application
            System.exit(2);
        }
    }
 
    protected abstract IElectionsUI getUI();
 
    private void afficheExceptions(String message, ElectionsException ex) {
        // the message
        System.out.println(String.format("%s -------------", message));
        System.out.println(String.format("Code erreur : %d", ex.getCode()));
        // errors are displayed
        for (String erreur : ex.getErreurs()) {
            System.out.println(String.format("-- %s", erreur));
        }
 
    }
 
    public void afficheExceptions(String message, Exception ex) {
        // the message
        System.out.println(String.format("%s -------------", message));
        // display the exception stack
        Throwable cause = ex;
        while (cause != null) {
            System.out.println(String.format("-- %s", cause.getMessage()));
            cause = cause.getCause();
        }
    }
}
  • Riga 19: viene istanziato il contesto Spring: verranno creati tutti i bean definiti nei vari file di configurazione;
  • riga 21: recuperiamo un riferimento al bean che implementa l'interfaccia [IElectionsUI]. Implementeremo l'interfaccia [IElectionsUI] utilizzando due bean:
    • [ElectionsConsole] per un'applicazione console;
    • [ElectionsSwing] per un'applicazione Swing;

Il metodo [getUI] è astratto (riga 44). Infatti, la classe [AbstractBootElections] sarà la classe padre di due classi:

  • [BootElectionsConsole], che fornirà il bean [ElectionsConsole] alla sua classe padre;
  • [BootElectionsSwing], che fornirà il bean [ElectionsSwing] alla sua classe padre;
  • riga 30: viene eseguito il metodo [run] dell'interfaccia;

Le eccezioni vengono gestite in diversi punti:

  • righe 22–27: un'eccezione può verificarsi durante l'istanziazione del contesto Spring;
  • righe 31–41: un'eccezione può verificarsi durante l'esecuzione del livello [UI]. Esistono due tipi di eccezioni:
    • la classe [ElectionsException] generata dal livello [DAO];
    • la classe [RuntimeException] per altre eccezioni che possono verificarsi durante l'esecuzione;

La classe [BootElectionsConsole] è la classe che avvia l'implementazione della console. Il suo codice è il seguente:


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);
    }
}
  • Riga 5: la classe [BootElectionsConsole] estende la classe [AbstractBootElections]. Deve quindi implementare il metodo [getUI] che la sua classe padre aveva dichiarato come astratto;
  • righe 10–13: implementazione del metodo [getUI];
  • riga 12: il bean denominato [electionsConsole] è configurato per implementare l'interfaccia [IElectionsUI]. [ctx] è il contesto Spring definito nella classe padre dalla dichiarazione:

    // spring context retrieval
    protected AnnotationConfigApplicationContext ctx;

Poiché il campo ha l'attributo [protected], è visibile nelle classi figlie.

Il bean alla riga 12 verrà dichiarato come segue:


@Component
public class ElectionsConsole implements IElectionsUI {

Il nome predefinito di questo bean è il nome della classe con la prima lettera minuscola. È possibile sovrascrivere questo nome predefinito scrivendo esplicitamente:

@Component(nom_du_bean_entre_guillemets)

La classe [BootElectionsSwing] che avvierebbe l'implementazione Swing potrebbe essere la seguente:


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);
    }
}

Questo modello di progettazione, in cui il comportamento comune alle classi viene raggruppato in una classe padre, mentre alle classi figlie viene lasciato il compito di implementarne i dettagli specifici, è chiamato modello di progettazione Strategy. Questo modello di progettazione impone un comportamento comune a tutte le classi figlie.

9.6. La classe di implementazione [ElectionsConsole]

  

La nostra prima classe di implementazione per il livello [ui] sarà una classe che utilizza la console per comunicare con l'utente. Ecco un esempio di finestra di dialogo visualizzata sulla console di 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] potrebbe avere la seguente struttura:


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() {
        // data entry
        try (Scanner clavier = new Scanner(System.in)) {
            // lists in competition are requested from the [metier] layer
 
            // we enter the votes
 
        }
        // we calculate the number of seats
 
        // we record the results
 
        // lists sorted in descending order of votes
 
        // we display them
    }
 
    // electoral list comparison class
    class CompareListesElectorales implements Comparator<ListeElectorale> {
 
        // comparison of two candidate lists by number of votes
        @Override
        public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
            // we compare the votes of these two lists
            int nbVoix1 = listeElectorale1.getVoix();
            int nbVoix2 = listeElectorale2.getVoix();
            if (nbVoix1 < nbVoix2) {
                return +1;
            } else {
                if (nbVoix1 > nbVoix2)
                    return -1;
                else
                    return 0;
            }
        }
    }
 
}
  • riga 12: la classe [ElectionsConsole] è un componente Spring;
  • riga 13: la classe implementa l'interfaccia [IElectionsUI]
  • righe 15–16: Spring inietta un riferimento nel livello [business];
  • righe 18–34: il metodo [run] dell'interfaccia [IelectionsUI];

Il blocco try alle righe 21–28 è denominato try-with-resources e la sua sintassi è la seguente:

1
2
3
try(ressource){
}

La risorsa alla riga 1 deve implementare l'interfaccia [java.lang.AutoCloseable]. La risorsa viene aperta alla riga 1 e chiusa automaticamente dopo la riga 3, indipendentemente dal fatto che si verifichi un'eccezione nel codice eseguito tra le due righe. Questa sintassi garantisce che una risorsa aperta venga chiusa, qualunque cosa accada.


Compito: Scrivere il codice per il metodo [run]. Utilizzare i commenti come guida.