Skip to content

9. [TD]: Implementacja warstwy [ui] za pomocą programu konsolowego

Słowa kluczowe: architektura wielowarstwowa, Spring, wstrzykiwanie zależności.

9.1. Support

W [1] folder [support / chap-09] zawiera projekt Eclipse warstwy [UI] aplikacji konsolowej.

9.2. Konfiguracja Maven

Projekt Eclipse o nazwie [elections-ui-metier-dao-jdbc] jest skonfigurowany za pomocą następującego pliku Maven o nazwie [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>
        <!-- funkcjonalny -->
        <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>

    <!-- wtyczki -->
    <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>
            <!-- do instalacji artefaktu projektu w lokalnym repozytorium Maven -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
</project>
  • wiersze 22–26: importuje się archiwum warstwy [métier], a w konsekwencji również archiwum warstwy [DAO];

9.3. Konfiguracja Spring

  

Klasa [UiConfig] konfiguruje aplikację 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 {
}
  • wiersz 8: importujemy komponenty zdefiniowane w warstwie [métier]. Warstwa ta importowała już komponenty zdefiniowane w warstwie [DAO]. Mamy więc tutaj dostęp do komponentów z trzech warstw;
  • wiersz 9: pakiet [elections.ui.service] zawiera inne komponenty;

9.4. Interfejs warstwy [UI]

  

Aby zrozumieć, czym może być interfejs Java warstwy [UI], musimy wiedzieć, kto będzie z niego korzystał. Nie chodzi tu o użytkownika przedstawionego na powyższym schemacie, ale o program, który uruchomi całą aplikację.

Interfejs [IElectionsUI] jest udostępniany programowi głównemu [main], który uruchomi aplikację. O co interfejs [main] może poprosić warstwę [ui]? Może poprosić ją o rozpoczęcie interakcji z użytkownikiem, który wprowadzi brakujące dane dotyczące wyborów. Przyjmiemy następujący minimalny interfejs:


package istia.st.elections.ui;

public interface IElectionsUI {
    /**
     * lance le dialogue avec l'utilisateur
     */
    public void run();
}
  • wiersz 7: interfejs posiada tylko jedną metodę: run. Wywołując tę metodę, prosimy warstwę [ui] o rozpoczęcie interakcji z użytkownikiem.

9.5. Klasa uruchamiająca aplikację

  

Klasa uruchamiająca aplikację to klasa Java posiadająca metodę statyczną [main]. Metoda ta musi utworzyć instancje warstw [ui, metier, demo] oraz polecić warstwie [ui] rozpoczęcie dialogu z użytkownikiem. Klasą tą mogłaby być następująca klasa [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 {

    // pobieranie kontekstu Spring
    protected AnnotationConfigApplicationContext ctx;

    public void run() {
        // instancjonowanie warstwy [ui]
        IElectionsUI electionsUI = null;
        try {
            // pobieranie kontekstu Spring
            ctx = new AnnotationConfigApplicationContext(UiConfig.class);
            // pobieranie warstwy [ui]
            electionsUI = getUI();
        } catch (RuntimeException ex) {
            // zgłaszanie błędu
            afficheExceptions("Les erreurs suivantes se sont produites :", ex);
            // zatrzymanie aplikacji
            System.exit(1);
        }
        // wykonanie warstwy [ui]
        try {
            electionsUI.run();
        } catch (ElectionsException ex2) {
            // zgłaszany jest błąd
            afficheExceptions("Les erreurs suivantes se sont produites :", ex2);
            // zatrzymuje się aplikację
            System.exit(3);
        } catch (RuntimeException ex1) {
            // zgłaszany jest błąd
            afficheExceptions("Les erreurs suivantes se sont produites :", ex1);
            // zamyka się aplikację
            System.exit(2);
        }
    }

    protected abstract IElectionsUI getUI();

    private void afficheExceptions(String message, ElectionsException ex) {
        // wyświetla się komunikat
        System.out.println(String.format("%s -------------", message));
        System.out.println(String.format("Code erreur : %d", ex.getCode()));
        // wyświetla się lista błędów
        for (String erreur : ex.getErreurs()) {
            System.out.println(String.format("-- %s", erreur));
        }

    }

    public void afficheExceptions(String message, Exception ex) {
        // wyświetla się komunikat
        System.out.println(String.format("%s -------------", message));
        // wyświetla stos wyjątków
        Throwable cause = ex;
        while (cause != null) {
            System.out.println(String.format("-- %s", cause.getMessage()));
            cause = cause.getCause();
        }
    }
}
  • wiersz 19: instancjonowany jest kontekst Spring: zostaną utworzone wszystkie bean’y zdefiniowane w różnych plikach konfiguracyjnych;
  • wiersz 21: pobierane jest odwołanie do bean’a, który implementuje interfejs [IElectionsUI]. Interfejs [IElectionsUI] zaimplementujemy za pomocą dwóch bean’ów:
    • [ElectionsConsole] dla aplikacji konsolowej;
    • [ElectionsSwing] dla aplikacji Swing;

Metoda [getUI] jest abstrakcyjna (wiersz 44). Klasa [AbstractBootElections] będzie bowiem klasą nadrzędną dla dwóch klas:

  • [BootElectionsConsole], która dostarczy bean [ElectionsConsole] swojej klasie nadrzędnej;
  • [BootElectionsSwing], która dostarczy bean [ElectionsSwing] swojej klasie nadrzędnej;
  • wiersz 30: wykonywana jest metoda [run] z interfejsu;

Wyjątki są obsługiwane w różnych miejscach:

  • wiersze 22–27: podczas instancjonowania kontekstu Spring może wystąpić wyjątek;
  • wiersze 31–41: podczas wykonywania warstwy [UI] może wystąpić wyjątek. Wyróżnia się dwa rodzaje wyjątków:
    • klasa [ElectionsException], którą wywołuje warstwa [DAO];
    • klasa [RuntimeException] dla pozostałych wyjątków, które mogą wystąpić podczas wykonywania;

Klasa [BootElectionsConsole] jest klasą uruchamiającą implementację konsolową. Jej kod wygląda następująco:


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);
    }
}
  • w wierszu 5 klasa [BootElectionsConsole] dziedziczy po klasie [AbstractBootElections]. Musi zatem zaimplementować metodę [getUI], którą jej klasa nadrzędna zadeklarowała jako abstrakcyjną;
  • wiersze 10–13: implementacja metody [getUI];
  • wiersz 12: tworzony jest bean o nazwie [electionsConsole], implementujący interfejs [IElectionsUI]. [ctx] to kontekst Spring zdefiniowany w klasie nadrzędnej poprzez deklarację:

    // pobieranie kontekstu Spring
    protected AnnotationConfigApplicationContext ctx;

Ponieważ pole posiada atrybut [protected], jest ono widoczne w klasach potomnych.

Bean z linii 12 zostanie zadeklarowany w następujący sposób:


@Component
public class ElectionsConsole implements IElectionsUI {

Domyślną nazwą tego bean jest nazwa klasy z małą pierwszą literą. Można odejść od tej domyślnej nazwy, wpisując wyraźnie:

@Component(nom_du_bean_entre_guillemets)

Klasa [BootElectionsSwing], która uruchamiałaby implementację Swing, mogłaby wyglądać następująco:


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

Ten model projektowy, w którym wspólne zachowanie klas jest wyodrębniane do klasy nadrzędnej, a klasy potomne implementują szczegóły specyficzne dla siebie, nazywa się modelem projektowym Strategy. Model ten narzuca wspólne zachowanie wszystkim klasom potomnym.

9.6. Klasa implementacyjna [ElectionsConsole]

  

Naszą pierwszą klasą implementacyjną warstwy [ui] będzie klasa wykorzystująca konsolę do komunikacji z użytkownikiem. Oto przykładowy dialog wyświetlany w konsoli 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]

Klasa [ElectionsConsole] mogłaby mieć następujący szkielet:


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() {
        // wprowadzanie danych
        try (Scanner clavier = new Scanner(System.in)) {
            // żądanie list konkurencyjnych z warstwy [metier]

            // przeprowadzane jest liczenie głosów

        }
        // przeprowadza się obliczenia dotyczące mandatów

        // rejestruje się wyniki

        // sortowanie list w porządku malejącym według liczby głosów

        // wyświetla się je
    }

    // klasa porównywania list wyborczych
    class CompareListesElectorales implements Comparator<ListeElectorale> {

        // porównanie dwóch list wyborczych pod względem liczby głosów
        @Override
        public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
            // porównanie liczby głosów tych dwóch list
            int nbVoix1 = listeElectorale1.getVoix();
            int nbVoix2 = listeElectorale2.getVoix();
            if (nbVoix1 < nbVoix2) {
                return +1;
            } else {
                if (nbVoix1 > nbVoix2)
                    return -1;
                else
                    return 0;
            }
        }
    }

}
  • wiersz 12: klasa [ElectionsConsole] jest komponentem Spring;
  • wiersz 13: klasa ta implementuje interfejs [IElectionsUI]
  • wiersze 15–16: wstrzyknięcie przez Spring odniesienia do warstwy [metier];
  • wiersze 18–34: metoda [run] interfejsu [IelectionsUI];

try z wierszy 21–28 nosi nazwę try-with-resources, a jego składnia jest następująca:

1
2
3
try(ressource){
}

Zasób z linii 1 musi implementować interfejs [java.lang.AutoCloseable]. Zasób jest otwierany w linii 1 i automatycznie zamykany po linii 3, niezależnie od tego, czy w kodzie wykonywanym między tymi dwiema liniami wystąpi wyjątek, czy nie. Taka składnia gwarantuje, że otwarty zasób zostanie zamknięty bez względu na okoliczności.


Zadanie: napisz kod metody [run]. Skorzystaj z komentarzy.