9. [TD]: Implementierung der [UI]-Schicht mit einem Konsolenprogramm
Stichworte: mehrschichtige Architektur, Spring, Dependency Injection.
![]() |
9.1. Support
![]() |
In [1] enthält der Ordner [support / chap-09] das Eclipse-Projekt für die [UI]-Schicht der Konsolenanwendung.
9.2. Maven-Konfiguration
Das Eclipse-Projekt [elections-ui-metier-dao-jdbc] wird durch die folgende Maven-Datei [pom.xml] konfiguriert:
<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>
- Zeilen 22–26: Wir importieren das Archiv der [Business]-Schicht und damit auch das Archiv der [DAO]-Schicht;
9.3. Spring-Konfiguration
![]() |
Die Klasse [UiConfig] konfiguriert die Spring-Anwendung:
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 {
}
- Zeile 8: Wir importieren die in der [business]-Schicht definierten Beans. Diese Schicht hat bereits die in der [DAO]-Schicht definierten Beans importiert. Wir haben daher hier Zugriff auf die Beans aus allen drei Schichten;
- Zeile 9: Das Paket [elections.ui.service] enthält weitere Beans;
9.4. Die Schnittstelle der [UI]-Schicht
![]() |
Um zu verstehen, wie die Java-Schnittstelle der [UI]-Schicht aussehen könnte, müssen wir wissen, wer diese Schnittstelle nutzen wird. Es ist nicht der Benutzer aus dem obigen Diagramm, sondern das Programm, das die Anwendung als Ganzes startet.
![]() |
Die [IElectionsUI]-Schnittstelle wird dem Hauptprogramm [main] bereitgestellt, das die Anwendung startet. Was kann [main] von der [UI]-Schicht verlangen? Es kann sie auffordern, Interaktionen mit dem Benutzer zu initiieren, der die fehlenden Wahldaten eingibt. Wir werden die folgende minimale Schnittstelle verwenden:
package istia.st.elections.ui;
public interface IElectionsUI {
/**
* lance le dialogue avec l'utilisateur
*/
public void run();
}
- Zeile 7: Die Schnittstelle hat nur eine Methode: run. Durch den Aufruf dieser Methode weisen wir die [ui]-Schicht an, die Interaktion mit dem Benutzer zu beginnen.
9.5. Der Einstiegspunkt der Anwendung
![]() |
Die Startklasse der Anwendung ist eine Java-Klasse mit einer statischen [main]-Methode. Diese Methode muss Instanzen der Schichten [ui, business, demo] erstellen und die [ui]-Schicht anweisen, die Interaktion mit dem Benutzer zu beginnen. Diese Klasse könnte die folgende [AbstractBootElections]-Klasse sein:
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();
}
}
}
- Zeile 19: Der Spring-Kontext wird instanziiert: Alle in den verschiedenen Konfigurationsdateien definierten Beans werden erstellt;
- Zeile 21: Wir rufen eine Referenz auf die Bean ab, die die Schnittstelle [IElectionsUI] implementiert. Wir werden die Schnittstelle [IElectionsUI] mithilfe von zwei Beans implementieren:
- [ElectionsConsole] für eine Konsolenanwendung;
- [ElectionsSwing] für eine Swing-Anwendung;
Die Methode [getUI] ist abstrakt (Zeile 44). Tatsächlich wird die Klasse [AbstractBootElections] die übergeordnete Klasse von zwei Klassen sein:
- [BootElectionsConsole], die der übergeordneten Klasse die [ElectionsConsole]-Bean bereitstellt;
- [BootElectionsSwing], die der übergeordneten Klasse die Bean [ElectionsSwing] bereitstellt;
- Zeile 30: Die Methode [run] der Schnittstelle wird ausgeführt;
Ausnahmen werden an verschiedenen Stellen behandelt:
- Zeilen 22–27: Bei der Instanziierung des Spring-Kontexts kann eine Ausnahme auftreten;
- Zeilen 31–41: Während der Ausführung der [UI]-Schicht kann eine Ausnahme auftreten. Es gibt zwei Arten von Ausnahmen:
- die Klasse [ElectionsException], die von der [DAO]-Schicht ausgelöst wird;
- die Klasse [RuntimeException] für andere Ausnahmen, die während der Ausführung auftreten können;
Die Klasse [BootElectionsConsole] ist die Klasse, die die Konsolenimplementierung startet. Ihr Code lautet wie folgt:
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);
}
}
- Zeile 5: Die Klasse [BootElectionsConsole] erweitert die Klasse [AbstractBootElections]. Sie muss daher die Methode [getUI] implementieren, die ihre übergeordnete Klasse als abstrakt deklariert hatte;
- Zeilen 10–13: Implementierung der Methode [getUI];
- Zeile 12: Die Bean mit dem Namen [electionsConsole] wird so gestaltet, dass sie die Schnittstelle [IElectionsUI] implementiert. [ctx] ist der Spring-Kontext, der in der übergeordneten Klasse durch die Deklaration definiert wurde:
// spring context retrieval
protected AnnotationConfigApplicationContext ctx;
Da das Feld das Attribut [protected] hat, ist es in untergeordneten Klassen sichtbar.
Die Bean in Zeile 12 wird wie folgt deklariert:
@Component
public class ElectionsConsole implements IElectionsUI {
Der Standardname dieser Bean ist der Klassenname, dessen erster Buchstabe klein geschrieben wird. Sie können diesen Standardnamen überschreiben, indem Sie explizit schreiben:
Die Klasse [BootElectionsSwing], die die Swing-Implementierung starten würde, könnte wie folgt aussehen:
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);
}
}
Dieses Entwurfsmuster, bei dem das allen Klassen gemeinsame Verhalten in eine übergeordnete Klasse ausgelagert wird und die Unterklassen ihre spezifischen Details selbst implementieren, wird als Strategie-Entwurfsmuster bezeichnet. Dieses Entwurfsmuster schreibt allen Unterklassen ein gemeinsames Verhalten vor.
9.6. Die Implementierungsklasse [ElectionsConsole]
![]() |
Unsere erste Implementierungsklasse für die [ui]-Ebene wird eine Klasse sein, die die Konsole nutzt, um mit dem Benutzer zu kommunizieren. Hier ist ein Beispiel für einen Dialog, der in der Eclipse-Konsole angezeigt wird:
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]
Die Klasse [ElectionsConsole] könnte folgendes Grundgerüst haben:
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;
}
}
}
}
- Zeile 12: Die Klasse [ElectionsConsole] ist eine Spring-Komponente;
- Zeile 13: Die Klasse implementiert die Schnittstelle [IElectionsUI]
- Zeilen 15–16: Spring injiziert eine Referenz in die [business]-Schicht;
- Zeilen 18–34: die Methode [run] der Schnittstelle [IelectionsUI];
Der try-Block in den Zeilen 21–28 wird als try-with-resources bezeichnet und hat folgende Syntax:
Die Ressource in Zeile 1 muss die Schnittstelle [java.lang.AutoCloseable] implementieren. Die Ressource wird in Zeile 1 geöffnet und nach Zeile 3 automatisch geschlossen, unabhängig davon, ob im zwischen diesen beiden Zeilen ausgeführten Code eine Ausnahme auftritt. Diese Syntax stellt sicher, dass eine geöffnete Ressource geschlossen wird, egal was passiert.
Aufgabe: Schreiben Sie den Code für die Methode [run]. Orientieren Sie sich dabei an den Kommentaren.






