9. [TD]: Implementação da camada [ui] com um programa de consola
Palavras-chave: arquitetura multicamadas, Spring, injeção de dependências.
![]() |
9.1. Suporte
![]() |
Em [1], a pasta [support / chap-09] contém o projeto Eclipse para a camada [UI] da aplicação de consola.
9.2. Configuração do Maven
O projeto Eclipse [elections-ui-metier-dao-jdbc] é configurado pelo seguinte ficheiro 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>
<!-- 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>
- linhas 22–26: importamos o arquivo da camada [business] e, por extensão, o arquivo da camada [DAO];
9.3. Configuração do Spring
![]() |
A classe [UiConfig] configura a aplicação 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 {
}
- Linha 8: Importamos os beans definidos na camada [business]. Esta camada já importou os beans definidos na camada [DAO]. Por isso, temos aqui acesso aos beans das três camadas;
- linha 9: o pacote [elections.ui.service] contém outros beans;
9.4. A interface da camada [UI]
![]() |
Para compreender como poderá ser a interface Java da camada [UI], precisamos de saber quem irá utilizar esta interface. Não é o utilizador do diagrama acima, mas sim o programa que irá iniciar a aplicação como um todo.
![]() |
A interface [IElectionsUI] é apresentada ao programa principal [main], que irá iniciar a aplicação. O que pode [main] solicitar à camada [UI]? Pode solicitar-lhe que inicie interações com o utilizador que irá introduzir os dados eleitorais em falta. Iremos adotar a seguinte interface mínima:
package istia.st.elections.ui;
public interface IElectionsUI {
/**
* lance le dialogue avec l'utilisateur
*/
public void run();
}
- Linha 7: A interface tem apenas um método: run. Ao chamar este método, instruímos a camada [ui] a começar a interagir com o utilizador.
9.5. O ponto de entrada da aplicação
![]() |
A classe de inicialização da aplicação é uma classe Java com um método estático [main]. Este método deve criar instâncias das camadas [ui, business, demo] e instruir a camada [ui] a começar a interagir com o utilizador. Esta classe poderia ser a seguinte classe [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 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();
}
}
}
- Linha 19: O contexto Spring é instanciado: todos os beans definidos nos vários ficheiros de configuração serão criados;
- linha 21: recuperamos uma referência ao bean que implementa a interface [IElectionsUI]. Iremos implementar a interface [IElectionsUI] utilizando dois beans:
- [ElectionsConsole] para uma aplicação de consola;
- [ElectionsSwing] para uma aplicação Swing;
O método [getUI] é abstrato (linha 44). Na verdade, a classe [AbstractBootElections] será a classe pai de duas classes:
- [BootElectionsConsole], que fornecerá o bean [ElectionsConsole] à sua classe pai;
- [BootElectionsSwing], que fornecerá o bean [ElectionsSwing] à sua classe pai;
- linha 30: o método [run] da interface é executado;
As exceções são tratadas em vários locais:
- linhas 22–27: pode ocorrer uma exceção durante a instanciação do contexto Spring;
- linhas 31–41: pode ocorrer uma exceção durante a execução da camada [UI]. Existem dois tipos de exceções:
- a classe [ElectionsException] lançada pela camada [DAO];
- a classe [RuntimeException] para outras exceções que possam ocorrer durante a execução;
A classe [BootElectionsConsole] é a classe que inicia a implementação da consola. O seu código é o seguinte:
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);
}
}
- Linha 5: A classe [BootElectionsConsole] estende a classe [AbstractBootElections]. Por conseguinte, deve implementar o método [getUI] que a sua classe pai declarou como abstrato;
- linhas 10–13: implementação do método [getUI];
- linha 12: o bean denominado [electionsConsole] é configurado para implementar a interface [IElectionsUI]. [ctx] é o contexto Spring definido na classe pai pela declaração:
// spring context retrieval
protected AnnotationConfigApplicationContext ctx;
Como o campo tem o atributo [protected], é visível nas classes filhas.
O bean na linha 12 será declarado da seguinte forma:
@Component
public class ElectionsConsole implements IElectionsUI {
O nome padrão deste bean é o nome da classe com a primeira letra minúscula. Pode substituir este nome padrão escrevendo explicitamente:
A classe [BootElectionsSwing] que iniciaria a implementação do Swing poderia ser a seguinte:
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);
}
}
Este padrão de design, no qual o comportamento comum às classes é agrupado numa classe pai, e as classes filhas ficam encarregadas de implementar os seus detalhes específicos, é chamado de padrão de design Strategy. Este padrão de design impõe um comportamento comum a todas as classes filhas.
9.6. A classe de implementação [ElectionsConsole]
![]() |
A nossa primeira classe de implementação para a camada [ui] será uma classe que utiliza a consola para comunicar com o utilizador. Aqui está um exemplo de uma caixa de diálogo apresentada na consola do 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]
A classe [ElectionsConsole] poderia ter a seguinte estrutura:
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;
}
}
}
}
- linha 12: a classe [ElectionsConsole] é um componente Spring;
- linha 13: a classe implementa a interface [IElectionsUI]
- linhas 15–16: o Spring injeta uma referência na camada [business];
- linhas 18–34: o método [run] da interface [IelectionsUI];
O bloco try nas linhas 21–28 é denominado try-with-resources, e a sua sintaxe é a seguinte:
O recurso na linha 1 deve implementar a interface [java.lang.AutoCloseable]. O recurso é aberto na linha 1 e fechado automaticamente após a linha 3, independentemente de ocorrer ou não uma exceção no código executado entre as duas linhas. Esta sintaxe garante que um recurso aberto será fechado, aconteça o que acontecer.
Tarefa: Escreva o código para o método [run]. Use os comentários como guia.






