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:
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:
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.






