Skip to content

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

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

9.1. Support

In [1], la cartella [support / chap-09] contiene il progetto Eclipse del livello [UI] dell'applicazione da 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>
        <!-- funzionale -->
        <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>
        <!-- Test Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <!-- plugin -->
    <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>
            <!-- per l'installazione dell'artifact del progetto nel repository Maven locale -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
</project>
  • righe 22-26: si importa l'archivio del livello [métier] e, di conseguenza, quello 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: si importano i bean definiti nel livello [métier]. Quest'ultimo importava già i bean definiti nel livello [DAO]. Si ha quindi accesso ai bean dei tre livelli;
  • riga 9: il pacchetto [elections.ui.service] contiene altri bean;

9.4. L'interfaccia del livello [UI]

  

Per comprendere quale possa essere l’interfaccia Java del livello [UI], dobbiamo sapere chi utilizzerà tale interfaccia. Non si tratta dell’utente dello schema 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ò richiedere [main] al livello [ui]? Può chiedergli di avviare l’interazione con l’utente che inserirà i dati mancanti relativi alle elezioni. Adotteremo la seguente interfaccia minima:


package istia.st.elections.ui;

public interface IElectionsUI {
    /**
     * lance le dialogue avec l'utilisateur
     */
    public void run();
}
  • riga 7: l’interfaccia ha un unico metodo: run. Richiamando questo metodo, si chiede al livello [ui] di avviare l’interazione con l’utente.

9.5. La classe di avvio dell’applicazione

  

La classe di avvio dell'applicazione è una classe Java dotata di un metodo statico [main]. Questo metodo deve creare istanze dei livelli [ui, metier, demo] e richiedere al livello [ui] di avviare il dialogo 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 {

    // recupero del contesto Spring
    protected AnnotationConfigApplicationContext ctx;

    public void run() {
        // istanziazione del livello [ui]
        IElectionsUI electionsUI = null;
        try {
            // recupero del contesto Spring
            ctx = new AnnotationConfigApplicationContext(UiConfig.class);
            // recupero del livello [ui]
            electionsUI = getUI();
        } catch (RuntimeException ex) {
            // si segnala l'errore
            afficheExceptions("Les erreurs suivantes se sont produites :", ex);
            // l'applicazione viene arrestata
            System.exit(1);
        }
        // esecuzione del livello [ui]
        try {
            electionsUI.run();
        } catch (ElectionsException ex2) {
            // viene segnalato l'errore
            afficheExceptions("Les erreurs suivantes se sont produites :", ex2);
            // si arresta l'applicazione
            System.exit(3);
        } catch (RuntimeException ex1) {
            // viene segnalato l'errore
            afficheExceptions("Les erreurs suivantes se sont produites :", ex1);
            // si arresta l'applicazione
            System.exit(2);
        }
    }

    protected abstract IElectionsUI getUI();

    private void afficheExceptions(String message, ElectionsException ex) {
        // viene visualizzato il messaggio
        System.out.println(String.format("%s -------------", message));
        System.out.println(String.format("Code erreur : %d", ex.getCode()));
        // vengono visualizzati gli errori
        for (String erreur : ex.getErreurs()) {
            System.out.println(String.format("-- %s", erreur));
        }

    }

    public void afficheExceptions(String message, Exception ex) {
        // si visualizza il messaggio
        System.out.println(String.format("%s -------------", message));
        // visualizza la pila delle eccezioni
        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: si recupera un riferimento al bean che implementa l'interfaccia [IElectionsUI]. Implementeremo l'interfaccia [IElectionsUI] tramite due bean:
    • [ElectionsConsole] per un'applicazione da 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 propria classe padre;
  • [BootElectionsSwing], che fornirà il bean [ElectionsSwing] alla propria classe padre;
  • riga 30: viene eseguito il metodo [run] dell'interfaccia;

Le eccezioni vengono gestite in vari punti:

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

La classe [BootElectionsConsole] è quella che avvia l'implementazione da 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);
    }
}
  • alla riga 5, la classe [BootElectionsConsole] estende la classe [AbstractBootElections]. Deve quindi implementare il metodo [getUI] che la sua classe padre aveva dichiarato astratto;
  • righe 10-13: implementazione del metodo [getUI];
  • riga 12: si crea il bean denominato [electionsConsole] che implementa l’interfaccia [IElectionsUI]. [ctx] è il contesto Spring definito nella classe padre tramite la dichiarazione:

    // recupero del contesto Spring
    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 ignorare 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 fattorizzato in una classe padre e le classi figlie implementano i dettagli a loro specifici, è denominato 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 del livello [ui] sarà una classe che utilizza la console per comunicare con l’utente. Ecco un esempio di dialogo ottenuto 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() {
        // inserimento dei dati
        try (Scanner clavier = new Scanner(System.in)) {
            // vengono richieste le liste in competizione al livello [metier]

            // si procede al conteggio dei voti

        }
        // si effettua il calcolo dei seggi

        // si registrano i risultati

        // ordinamento delle liste in ordine decrescente di voti

        // vengono visualizzate
    }

    // classe di confronto delle liste elettorali
    class CompareListesElectorales implements Comparator<ListeElectorale> {

        // confronto tra due liste candidate in base al numero di voti
        @Override
        public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
            // si confrontano i voti di queste due liste
            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: iniezione da parte di Spring di un riferimento al livello [metier];
  • righe 18-34: il metodo [run] dell'interfaccia [IelectionsUI];

Il try delle righe 21-28 si chiama try-with-resources e la sua sintassi è la seguente:

1
2
3
try(ressource){
}

La risorsa della 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 o meno un'eccezione nel codice eseguito tra le due righe. Questa sintassi garantisce che una risorsa aperta venga chiusa correttamente, qualunque cosa accada.


Compito da svolgere: scrivere il codice del metodo [run]. Ci si avvarrà dei commenti.