9. [TD]: Implementación de la capa [ui] con un programa de consola
Palabras clave: arquitectura multicapa, Spring, inyección de dependencias.
![]() |
9.1. Support
![]() |
En [1], la carpeta [support / chap-09] contiene el proyecto Eclipse de la capa [UI] de la aplicación de consola.
9.2. Configuración de Maven
El proyecto de Eclipse [elections-ui-metier-dao-jdbc] se configura mediante el siguiente archivo 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>
<!-- función -->
<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>
<!-- Prueba de Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- complementos -->
<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>
<!-- para la instalación del artefacto del proyecto en el repositorio local de Maven -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- líneas 22-26: se importa el archivo de la capa [métier] y, de forma cascada, el de la capa [DAO];
9.3. Configuración de Spring
![]() |
La clase [UiConfig] configura la aplicación 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 {
}
- línea 8: se importan los beans definidos en la capa [métier]. Esta ya importaba los beans definidos en la capa [DAO]. Por lo tanto, aquí se tiene acceso a los beans de las tres capas;
- línea 9: el paquete [elections.ui.service] contiene otros beans;
9.4. La interfaz de la capa [UI]
![]() |
Para comprender cómo puede ser la interfaz Java de la capa [UI], debemos saber quién va a utilizar esta interfaz. No se trata del usuario del esquema anterior, sino del programa que va a iniciar la aplicación en su conjunto.
![]() |
La interfaz [IElectionsUI] se presenta al programa principal [main], que va a iniciar la aplicación. ¿Qué puede solicitar [main] a la capa [ui]? Puede pedirle que inicie la interacción con el usuario que va a introducir los datos que faltan de las elecciones. Se adoptará la siguiente interfaz mínima:
package istia.st.elections.ui;
public interface IElectionsUI {
/**
* lance le dialogue avec l'utilisateur
*/
public void run();
}
- línea 7: la interfaz solo tiene un único método: run. Al llamar a este método, se solicita a la capa [ui] que inicie la interacción con el usuario.
9.5. La clase de inicio de la aplicación
![]() |
La clase de inicio de la aplicación es una clase Java que tiene un método estático [main]. Este método debe crear instancias de las capas [ui, metier, demo] y solicitar a la capa [ui] que inicie el diálogo con el usuario. Esta clase podría ser la siguiente clase [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 {
// Recuperación del contexto de Spring
protected AnnotationConfigApplicationContext ctx;
public void run() {
// instanciación de la capa [ui]
IElectionsUI electionsUI = null;
try {
// recuperación del contexto de Spring
ctx = new AnnotationConfigApplicationContext(UiConfig.class);
// Recuperación de la capa [ui]
electionsUI = getUI();
} catch (RuntimeException ex) {
// se notifica el error
afficheExceptions("Les erreurs suivantes se sont produites :", ex);
// se detiene la aplicación
System.exit(1);
}
// ejecución de la capa [ui]
try {
electionsUI.run();
} catch (ElectionsException ex2) {
// se notifica el error
afficheExceptions("Les erreurs suivantes se sont produites :", ex2);
// se detiene la aplicación
System.exit(3);
} catch (RuntimeException ex1) {
// se notifica el error
afficheExceptions("Les erreurs suivantes se sont produites :", ex1);
// se cierra la aplicación
System.exit(2);
}
}
protected abstract IElectionsUI getUI();
private void afficheExceptions(String message, ElectionsException ex) {
// se muestra el mensaje
System.out.println(String.format("%s -------------", message));
System.out.println(String.format("Code erreur : %d", ex.getCode()));
// se muestran los errores
for (String erreur : ex.getErreurs()) {
System.out.println(String.format("-- %s", erreur));
}
}
public void afficheExceptions(String message, Exception ex) {
// se muestra el mensaje
System.out.println(String.format("%s -------------", message));
// se muestra la pila de excepciones
Throwable cause = ex;
while (cause != null) {
System.out.println(String.format("-- %s", cause.getMessage()));
cause = cause.getCause();
}
}
}
- línea 19: se instancia el contexto de Spring: se crearán todos los beans definidos en los distintos archivos de configuración;
- línea 21: se obtiene una referencia al bean que implementa la interfaz [IElectionsUI]. Vamos a implementar la interfaz [IElectionsUI] mediante dos beans:
- [ElectionsConsole] para una aplicación de consola;
- [ElectionsSwing] para una aplicación Swing;
El método [getUI] es abstracto (línea 44). De hecho, la clase [AbstractBootElections] será la clase padre de dos clases:
- [BootElectionsConsole], que proporcionará el bean [ElectionsConsole] a su clase padre;
- [BootElectionsSwing], que proporcionará el bean [ElectionsSwing] a su clase padre;
- línea 30: se ejecuta el método [run] de la interfaz;
Las excepciones se gestionan en varios puntos:
- líneas 22-27: puede producirse una excepción al instanciar el contexto de Spring;
- líneas 31-41: puede producirse una excepción durante la ejecución de la capa [UI]. Se distinguen dos tipos de excepción:
- la clase [ElectionsException] que lanza la capa [DAO];
- la clase [RuntimeException] para las demás excepciones que pudieran producirse durante la ejecución;
La clase [BootElectionsConsole] es la que inicia la implementación de la consola. Su código es el siguiente:
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);
}
}
- En la línea 5, la clase [BootElectionsConsole] hereda de la clase [AbstractBootElections]. Por lo tanto, debe implementar el método [getUI] que su clase padre había declarado como abstracto;
- líneas 10-13: implementación del método [getUI];
- línea 12: se crea el bean denominado [electionsConsole] que implementa la interfaz [IElectionsUI]. [ctx] es el contexto de Spring definido en la clase padre mediante la declaración:
// se recupera el contexto de Spring
protected AnnotationConfigApplicationContext ctx;
Dado que el campo tiene el atributo [protected], es visible en las clases hijas.
El bean de la línea 12 se declarará de la siguiente manera:
@Component
public class ElectionsConsole implements IElectionsUI {
El nombre por defecto de este bean es el nombre de la clase con la primera letra en minúscula. Se puede evitar este nombre por defecto escribiendo explícitamente:
La clase [BootElectionsSwing], que iniciaría la implementación de Swing, podría ser la siguiente:
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 modelo de diseño, en el que se factoriza el comportamiento común a varias clases en una clase padre y se deja que las clases hijas implementen los detalles que les son específicos, se denomina modelo de diseño Strategy. Este modelo de diseño impone un comportamiento común a todas las clases hijas.
9.6. La clase de implementación [ElectionsConsole]
![]() |
Nuestra primera clase de implementación de la capa [ui] será una clase que utilice la consola para comunicarse con el usuario. A continuación se muestra un ejemplo de diálogo obtenido en la consola de 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]
La clase [ElectionsConsole] podría tener la estructura siguiente:
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() {
// introducción de datos
try (Scanner clavier = new Scanner(System.in)) {
// se solicitan las listas en competición a la capa [metier]
// se realiza el recuento de votos
}
// se calculan los escaños
// se registran los resultados
// se ordenan las listas por orden descendente de votos
// se muestran
}
// clase de comparación de listas electorales
class CompareListesElectorales implements Comparator<ListeElectorale> {
// comparación de dos listas de candidatos según el número de votos
@Override
public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
// se comparan los votos de estas dos listas
int nbVoix1 = listeElectorale1.getVoix();
int nbVoix2 = listeElectorale2.getVoix();
if (nbVoix1 < nbVoix2) {
return +1;
} else {
if (nbVoix1 > nbVoix2)
return -1;
else
return 0;
}
}
}
}
- línea 12: la clase [ElectionsConsole] es un componente de Spring;
- línea 13: la clase implementa la interfaz [IElectionsUI]
- líneas 15-16: inyección por parte de Spring de una referencia en la capa [metier];
- líneas 18-34: el método [run] de la interfaz [IelectionsUI];
El try de las líneas 21-28 se denomina try-with-resources y su sintaxis es la siguiente:
El recurso de la línea 1 debe implementar la interfaz [java.lang.AutoCloseable]. El recurso se abre en la línea 1 y se cierra automáticamente tras la línea 3, independientemente de si se produce o no una excepción en el código ejecutado entre ambas líneas. Esta sintaxis garantiza que un recurso abierto se cierre correctamente, pase lo que pase.
Tarea: escribe el código del método [run]. Utiliza los comentarios como ayuda.






