Skip to content

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:

@Component(nom_du_bean_entre_guillemets)

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:

1
2
3
try(ressource){
}

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.