Skip to content

9. [TD]: Implementatie van de laag [ui] met een consoleprogramma

Sleutelwoorden: meerlaagse architectuur, Spring, afhankelijkheidsinjectie.

9.1. Support

In [1] bevat de map [support / chap-09] het Eclipse-project van de laag [UI] van de console-applicatie.

9.2. Maven-configuratie

Het Eclipse-project [elections-ui-metier-dao-jdbc] wordt geconfigureerd door het volgende Maven-bestand [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>
        <!-- bedrijfsspecifiek -->
        <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>
            <!-- voor het installeren van het projectartefact in de lokale Maven-repository -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
</project>
  • regels 22-26: het archief van de laag [métier] wordt geïmporteerd en vervolgens dat van de laag [DAO];

9.3. Spring-configuratie

  

De klasse [UiConfig] configureert de Spring-applicatie:


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 {
}
  • regel 8: de beans die zijn gedefinieerd in de laag [métier] worden geïmporteerd. Deze laag importeerde al de beans die zijn gedefinieerd in de laag [DAO]. We hebben hier dus toegang tot de beans van de drie lagen;
  • regel 9: het pakket [elections.ui.service] bevat nog andere beans;

9.4. De interface van de laag [UI]

  

Om te begrijpen hoe de Java-interface van de laag [UI] eruit zou kunnen zien, moeten we weten wie deze interface gaat gebruiken. Het gaat hier niet om de gebruiker uit het bovenstaande schema, maar om het programma dat de applicatie in zijn geheel gaat starten.

De interface [IElectionsUI] wordt aangeboden aan het hoofdprogramma [main], dat de applicatie zal starten. Wat kan [main] van de laag [ui] vragen? Het kan deze laag vragen om de communicatie te starten met de gebruiker die de ontbrekende gegevens van de verkiezing zal invoeren. We hanteren de volgende minimale interface:


package istia.st.elections.ui;

public interface IElectionsUI {
    /**
     * lance le dialogue avec l'utilisateur
     */
    public void run();
}
  • regel 7: de interface heeft slechts één methode: run. Door deze methode aan te roepen, wordt de laag [ui] gevraagd om de communicatie met de gebruiker te starten.

9.5. De startklasse van de applicatie

  

De startklasse van de applicatie is een Java-klasse met een statische methode [main]. Deze methode moet instanties van de lagen [ui, metier, demo] aanmaken en de laag [ui] vragen om de dialoog met de gebruiker te starten. Deze klasse zou de volgende klasse [AbstractBootElections] kunnen zijn:


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 {

    // het ophalen van de Spring-context
    protected AnnotationConfigApplicationContext ctx;

    public void run() {
        // instantiëren van de [ui]-laag
        IElectionsUI electionsUI = null;
        try {
            // het ophalen van de Spring-context
            ctx = new AnnotationConfigApplicationContext(UiConfig.class);
            // het ophalen van de laag [ui]
            electionsUI = getUI();
        } catch (RuntimeException ex) {
            // de fout wordt gemeld
            afficheExceptions("Les erreurs suivantes se sont produites :", ex);
            // de applicatie wordt gestopt
            System.exit(1);
        }
        // uitvoering van de laag [ui]
        try {
            electionsUI.run();
        } catch (ElectionsException ex2) {
            // de fout wordt gemeld
            afficheExceptions("Les erreurs suivantes se sont produites :", ex2);
            // de toepassing wordt gestopt
            System.exit(3);
        } catch (RuntimeException ex1) {
            // de fout wordt gemeld
            afficheExceptions("Les erreurs suivantes se sont produites :", ex1);
            // de toepassing wordt afgesloten
            System.exit(2);
        }
    }

    protected abstract IElectionsUI getUI();

    private void afficheExceptions(String message, ElectionsException ex) {
        // het bericht wordt weergegeven
        System.out.println(String.format("%s -------------", message));
        System.out.println(String.format("Code erreur : %d", ex.getCode()));
        // de fouten worden weergegeven
        for (String erreur : ex.getErreurs()) {
            System.out.println(String.format("-- %s", erreur));
        }

    }

    public void afficheExceptions(String message, Exception ex) {
        // het bericht wordt weergegeven
        System.out.println(String.format("%s -------------", message));
        // de uitzonderingsstapel wordt weergegeven
        Throwable cause = ex;
        while (cause != null) {
            System.out.println(String.format("-- %s", cause.getMessage()));
            cause = cause.getCause();
        }
    }
}
  • regel 19: de Spring-context wordt geïnstantieerd: alle beans die in de verschillende configuratiebestanden zijn gedefinieerd, worden aangemaakt;
  • regel 21: er wordt een verwijzing opgehaald naar de bean die de interface [IElectionsUI] implementeert. We gaan de interface [IElectionsUI] implementeren met twee beans:
    • [ElectionsConsole] voor een console-applicatie;
    • [ElectionsSwing] voor een Swing-toepassing;

De methode [getUI] is abstract (regel 44). De klasse [AbstractBootElections] zal namelijk de bovenliggende klasse zijn van twee klassen:

  • [BootElectionsConsole], die de bean [ElectionsConsole] aan zijn bovenliggende klasse zal leveren;
  • [BootElectionsSwing], die de bean [ElectionsSwing] aan zijn bovenliggende klasse zal leveren;
  • regel 30: de methode [run] van de interface wordt uitgevoerd;

Uitzonderingen worden op verschillende plaatsen afgehandeld:

  • regels 22-27: er kan een uitzondering optreden bij het instantiëren van de Spring-context;
  • regels 31-41: er kan een uitzondering optreden tijdens de uitvoering van de laag [UI]. Er worden twee soorten uitzonderingen onderscheiden:
    • de klasse [ElectionsException] die door de laag [DAO] wordt gegenereerd;
    • de klasse [RuntimeException] voor andere uitzonderingen die tijdens de uitvoering kunnen optreden;

De klasse [BootElectionsConsole] is de klasse die de console-implementatie start. De code ervan is als volgt:


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);
    }
}
  • regel 5: de klasse [BootElectionsConsole] is een afgeleide van de klasse [AbstractBootElections]. Deze moet dus de methode [getUI] implementeren die door de bovenliggende klasse als abstract was gedeclareerd;
  • regels 10-13: implementatie van de methode [getUI];
  • regel 12: we maken de bean met de naam [electionsConsole] die de interface [IElectionsUI] implementeert. [ctx] is de Spring-context die in de bovenliggende klasse is gedefinieerd door de declaratie:

    // de Spring-context ophalen
    protected AnnotationConfigApplicationContext ctx;

Omdat het veld het attribuut [protected] heeft, is het zichtbaar in de onderliggende klassen.

De bean op regel 12 wordt als volgt gedeclareerd:


@Component
public class ElectionsConsole implements IElectionsUI {

De standaardnaam van deze bean is de naam van de klasse met de eerste letter in kleine letters. Je kunt deze standaardnaam omzeilen door expliciet het volgende te schrijven:

@Component(nom_du_bean_entre_guillemets)

De klasse [BootElectionsSwing], die de Swing-implementatie zou starten, zou er als volgt uit kunnen zien:


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

Dit ontwerpmodel, waarbij het gemeenschappelijke gedrag van klassen wordt ondergebracht in een bovenliggende klasse en de onderliggende klassen de specifieke details zelf implementeren, wordt het Strategy-ontwerpmodel genoemd. Dit ontwerpmodel legt een gemeenschappelijk gedrag op aan alle onderliggende klassen.

9.6. De implementatieklasse [ElectionsConsole]

  

Onze eerste implementatieklasse van de laag [ui] zal een klasse zijn die de console gebruikt om met de gebruiker te communiceren. Hier volgt een voorbeeld van een dialoogvenster in de Eclipse-console:


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]

De klasse [ElectionsConsole] zou de volgende basisstructuur kunnen hebben:


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() {
        // gegevens invoeren
        try (Scanner clavier = new Scanner(System.in)) {
            // de concurrerende lijsten worden opgevraagd bij de laag [metier]

            // de stemmen worden ingevoerd

        }
        // de zetels worden berekend

        // de resultaten worden geregistreerd

        // de lijsten worden gesorteerd in aflopende volgorde van het aantal stemmen

        // deze worden weergegeven
    }

    // vergelijkingsklasse van kieslijsten
    class CompareListesElectorales implements Comparator<ListeElectorale> {

        // vergelijking van twee kandidatenlijsten op basis van het aantal stemmen
        @Override
        public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
            // de stemmen van deze twee lijsten worden vergeleken
            int nbVoix1 = listeElectorale1.getVoix();
            int nbVoix2 = listeElectorale2.getVoix();
            if (nbVoix1 < nbVoix2) {
                return +1;
            } else {
                if (nbVoix1 > nbVoix2)
                    return -1;
                else
                    return 0;
            }
        }
    }

}
  • regel 12: de klasse [ElectionsConsole] is een Spring-component;
  • regel 13: de klasse implementeert de interface [IElectionsUI]
  • regels 15-16: injectie door Spring van een verwijzing naar de laag [metier];
  • regels 18-34: de methode [run] van de interface [IelectionsUI];

De try in de regels 21-28 heet try-with-resources en de syntaxis ervan is als volgt:

1
2
3
try(ressource){
}

De resource in regel 1 moet de interface [java.lang.AutoCloseable] implementeren. De resource wordt geopend in regel 1 en automatisch gesloten na regel 3, ongeacht of er al dan niet een uitzondering optreedt in de code die tussen deze twee regels wordt uitgevoerd. Deze syntaxis zorgt ervoor dat een geopende resource altijd wordt gesloten, wat er ook gebeurt.


Te doen: schrijf de code voor de methode [run]. Gebruik daarbij de commentaren als hulp.