9. [TD]: Реалізація шару [ui] за допомогою консольної програми
Ключові слова: багатошарова архітектура, Spring, ін’єкція залежностей.
![]() |
9.1. Support
![]() |
У [1] папка [support / chap-09] містить проект Eclipse для шару [UI] консольного додатка.
9.2. Конфігурація Maven
Проєкт Eclipse [elections-ui-metier-dao-jdbc] налаштовується за допомогою такого файлу 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>
<!-- бізнес-процес -->
<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 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- плагіни -->
<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>
<!-- для встановлення артефакту проєкту в локальне сховище Maven -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- рядки 22–26: імпортується архів шару [métier], а за ним — архів шару [DAO];
9.3. Конфігурація Spring
![]() |
Клас [UiConfig] налаштовує додаток 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 {
}
- рядок 8: імпортуються біни, визначені в шарі [métier]. Цей шар уже імпортував біни, визначені в шарі [DAO]. Отже, тут є доступ до бінів усіх трьох шарів;
- рядок 9: пакет [elections.ui.service] містить інші біни;
9.4. Інтерфейс шару [UI]
![]() |
Щоб зрозуміти, яким може бути Java-інтерфейс шару [UI], нам потрібно знати, хто буде використовувати цей інтерфейс. Мова йде не про користувача схеми, наведеної вище, а про програму, яка запускатиме додаток у цілому.
![]() |
Інтерфейс [IElectionsUI] надається головній програмі [main], яка запускатиме додаток. Що може вимагати [main] від рівня [ui]? Вона може вимагати від нього розпочати взаємодію з користувачем, який введе відсутні дані про вибори. Ми використаємо такий мінімальний інтерфейс:
package istia.st.elections.ui;
public interface IElectionsUI {
/**
* lance le dialogue avec l'utilisateur
*/
public void run();
}
- рядок 7: інтерфейс має лише один метод — run. Викликаючи цей метод, ми просимо рівень [ui] розпочати взаємодію з користувачем.
9.5. Клас запуску додатка
![]() |
Клас запуску додатка — це клас Java, що має статичний метод [main]. Цей метод повинен створити екземпляри шарів [ui, metier, demo] та доручити шару [ui] розпочати діалог з користувачем. Цим класом може бути наступний клас [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 {
// отримання контексту Spring
protected AnnotationConfigApplicationContext ctx;
public void run() {
// інстанціювання шару [ui]
IElectionsUI electionsUI = null;
try {
// отримання контексту Spring
ctx = new AnnotationConfigApplicationContext(UiConfig.class);
// отримання шару [ui]
electionsUI = getUI();
} catch (RuntimeException ex) {
// повідомлення про помилку
afficheExceptions("Les erreurs suivantes se sont produites :", ex);
// застосунок зупиняється
System.exit(1);
}
// виконання шару [ui]
try {
electionsUI.run();
} catch (ElectionsException ex2) {
// повідомляється про помилку
afficheExceptions("Les erreurs suivantes se sont produites :", ex2);
// застосунок зупинено
System.exit(3);
} catch (RuntimeException ex1) {
// повідомлення про помилку
afficheExceptions("Les erreurs suivantes se sont produites :", ex1);
// завершується робота програми
System.exit(2);
}
}
protected abstract IElectionsUI getUI();
private void afficheExceptions(String message, ElectionsException ex) {
// відображається повідомлення
System.out.println(String.format("%s -------------", message));
System.out.println(String.format("Code erreur : %d", ex.getCode()));
// виводить перелік помилок
for (String erreur : ex.getErreurs()) {
System.out.println(String.format("-- %s", erreur));
}
}
public void afficheExceptions(String message, Exception ex) {
// виводиться повідомлення
System.out.println(String.format("%s -------------", message));
// виводиться стек винятків
Throwable cause = ex;
while (cause != null) {
System.out.println(String.format("-- %s", cause.getMessage()));
cause = cause.getCause();
}
}
}
- рядок 19: створюється екземпляр контексту Spring: будуть створені всі біни, визначені в різних файлах конфігурації;
- рядок 21: отримується посилання на бін, що реалізує інтерфейс [IElectionsUI]. Ми реалізуємо інтерфейс [IElectionsUI] за допомогою двох бінів:
- [ElectionsConsole] для консольного додатка;
- [ElectionsSwing] — для Swing-додатка;
Метод [getUI] є абстрактним (рядок 44). Дійсно, клас [AbstractBootElections] буде батьківським для двох класів:
- [BootElectionsConsole], який надаватиме бін [ElectionsConsole] своєму батьківському класу;
- [BootElectionsSwing], який надаватиме бін [ElectionsSwing] своєму батьківському класу;
- рядок 30: виконується метод [run] інтерфейсу;
Винятки обробляються в різних місцях:
- рядки 22–27: під час інстанціювання контексту Spring може виникнути виняток;
- рядки 31–41: під час виконання шару [UI] може виникнути виняток. Розрізняють два типи винятків:
- клас [ElectionsException], який запускає рівень [DAO];
- клас [RuntimeException] для інших винятків, які можуть виникнути під час виконання;
Клас [BootElectionsConsole] — це клас, який запускає консольну реалізацію. Його код такий:
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);
}
}
- у рядку 5 клас [BootElectionsConsole] успадковує клас [AbstractBootElections]. Отже, він повинен реалізувати метод [getUI], який його батьківський клас оголосив абстрактним;
- рядки 10–13: реалізація методу [getUI];
- рядок 12: створюється бін із назвою [electionsConsole], що реалізує інтерфейс [IElectionsUI]. [ctx] — це контекст Spring, визначений у батьківському класі за допомогою оголошення:
// отримання контексту Spring
protected AnnotationConfigApplicationContext ctx;
Оскільки поле має атрибут [protected], воно є видимим у дочірніх класах.
Bean у рядку 12 буде оголошено наступним чином:
@Component
public class ElectionsConsole implements IElectionsUI {
Ім'я цього біна за замовчуванням — це назва класу з першою літерою, написаною з малої літери. Можна відійти від цього імені за замовчуванням, явно вказавши:
Клас [BootElectionsSwing], який запускає реалізацію Swing, може виглядати так:
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);
}
}
Ця модель проектування, в якій спільну поведінку класів виокремлюють у батьківський клас, а дочірнім класам залишають реалізацію їхніх специфічних деталей, називається моделлю проектування Strategy. Ця модель проектування накладає спільну поведінку на всі дочірні класи.
9.6. Клас реалізації [ElectionsConsole]
![]() |
Нашим першим класом реалізації рівня [ui] буде клас, що використовує консоль для взаємодії з користувачем. Ось приклад діалогу, отриманого в консолі 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]
Клас [ElectionsConsole] може мати такий каркас:
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() {
// введення даних
try (Scanner clavier = new Scanner(System.in)) {
// запит списків, що конкурують, до рівня [metier]
// здійснюється підрахунок голосів
}
// проводиться підрахунок місць
// результати зберігаються
// сортування списків у порядку спадання кількості голосів
// вони відображаються
}
// клас порівняння виборчих списків
class CompareListesElectorales implements Comparator<ListeElectorale> {
// порівняння двох списків кандидатів за кількістю голосів
@Override
public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
// порівняння голосів цих двох списків
int nbVoix1 = listeElectorale1.getVoix();
int nbVoix2 = listeElectorale2.getVoix();
if (nbVoix1 < nbVoix2) {
return +1;
} else {
if (nbVoix1 > nbVoix2)
return -1;
else
return 0;
}
}
}
}
- рядок 12: клас [ElectionsConsole] є компонентом Spring;
- рядок 13: клас реалізує інтерфейс [IElectionsUI]
- рядки 15–16: ін'єкція Spring-ом посилання на рівень [metier];
- рядки 18–34: метод [run] інтерфейсу [IelectionsUI];
try у рядках 21–28 називається try-with-resources, і його синтаксис такий:
Ресурс у рядку 1 повинен реалізовувати інтерфейс [java.lang.AutoCloseable]. Ресурс відкривається у рядку 1 і автоматично закривається після рядка 3, незалежно від того, чи виникне виняток у коді, що виконується між цими двома рядками. Цей синтаксис дозволяє гарантувати, що відкритий ресурс буде обов’язково закритий, що б не сталося.
Завдання: напишіть код методу [run]. Скористайтеся коментарями.






