Skip to content

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 {

Ім'я цього біна за замовчуванням — це назва класу з першою літерою, написаною з малої літери. Можна відійти від цього імені за замовчуванням, явно вказавши:

@Component(nom_du_bean_entre_guillemets)

Клас [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
2
3
try(ressource){
}

Ресурс у рядку 1 повинен реалізовувати інтерфейс [java.lang.AutoCloseable]. Ресурс відкривається у рядку 1 і автоматично закривається після рядка 3, незалежно від того, чи виникне виняток у коді, що виконується між цими двома рядками. Цей синтаксис дозволяє гарантувати, що відкритий ресурс буде обов’язково закритий, що б не сталося.


Завдання: напишіть код методу [run]. Скористайтеся коментарями.