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 de 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>
        <!-- de negocio -->
        <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>

    <!-- 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>
            <!-- para instalar el 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 manera 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í tenemos 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 entender cómo podría ser la interfaz Java de la capa [UI], debemos saber quién utilizará esta interfaz. No se trata del usuario del diagrama anterior, sino del programa que ejecutará la aplicación en su conjunto.

La interfaz [IElectionsUI] se presenta al programa principal [main], que iniciará la aplicación. ¿Qué puede solicitar [main] a la capa [ui]? Puede pedirle que inicie la interacción con el usuario que ingresará los datos que faltan de la elección. 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 método: run. Al llamar a este método, se le pide 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 cuenta con 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 reporta 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 reporta el error
            afficheExceptions("Les erreurs suivantes se sont produites :", ex2);
            // se detiene la aplicación
            System.exit(3);
        } catch (RuntimeException ex1) {
            // se reporta el error
            afficheExceptions("Les erreurs suivantes se sont produites :", ex1);
            // se detiene 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]. Implementaremos 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 manejan en varios lugares:

  • líneas 22-27: puede ocurrir 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 surgir 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] extiende 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 predeterminado de este bean es el nombre de la clase con la primera letra en minúscula. Se puede evitar este nombre predeterminado escribiendo explícitamente:

@Component(nom_du_bean_entre_guillemets)

La clase [BootElectionsSwing], que ejecutarí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() {
        // ingreso de datos
        try (Scanner clavier = new Scanner(System.in)) {
            // se solicitan las listas en competencia a la capa [metier]

            // se realiza el recuento de votos

        }
        // se calculan los escaños

        // se registran los resultados

        // se ordenan las listas en orden descendente según el número 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: Spring inyecta 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 llama 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 después de la línea 3, independientemente de si hay 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 guía.