Programación Avanzada

Interfaces gráficas de usuario

Programación dirigida por eventos

Introducción

La mayor parte de las aplicaciones informáticas ofrecen, al usuario que las utiliza, una interfaz gráfica.

Estas interfaces gráficas están compuestas por componentes (botones, listas, cajas de edición de texto, etc.), con los que el usuario interacción para llevar a cabo su tarea.

Es el usuario el que, en cada momento, decide qué es lo siguiente que la aplicación va a hacer a través de su interacción.

La aplicación debe responder a la interacción del usuario.

Nosotros, como programadores, debemos escribir el código de respuesta a la interacción del usuario.

Bibliografía

Contenidos

  1. Las interfaces de texto son tediosas.
  2. Las interfaces gráficas molan.
  3. Dime tú lo que quieres hacer.
  4. El patrón de diseño Observador/Observable
  5. El (meta) patrón de diseño Modelo Vista Controlador.
  6. Un ejemplo de aplicación que implementa MVC.

Las interfaces de texto son tediosas

  • Son poco intuitivas.
  • El usuario puede cometer un error fácilmente.
  • Pueden ser buenas para tareas administrativas.
  • En general consumen pocos recursos.

Las interfaces de texto son tediosas

  • Autoexplicativas (si están bien diseñadas).
  • Conjunto estándar de componentes.
  • Consumen, en general, más recursos.
  • Concurrencia (muchos elementos activos en la misma vista).

Dime tú lo que quieres hacer

Pero la gran diferencia radica en el modelo de programación.

En las interfaces de texto, la aplicación es la que inicia la interacción con el usuario, y este responde.

La interacción está dirigida por la aplicación.

Dime tú lo que quieres hacer

Vídeo

Dime tú lo que quieres hacer

En las interfaces gráficos, es el usuario quien inicia la interacción y la aplicación responde.

Las interfaces gráficas están centradas en el usuario, las de texto en la máquina.

Dime tú lo que quieres hacer

Vídeo

Dime tú lo que quieres hacer

En las interfaces gráficas, el usuario interacciona con distintos elementos de la interfaz (ventanas, botones, solapas, etcétera), y la aplicación responde en consecuencia.

Este nuevo modelo de programación se llama Programación Dirigida por Eventos.

Dime tú lo que quieres hacer

Cada interacción del usuario con la interfaz gráfica genera uno o más eventos. El programador escribe el código de respuesta a estos eventos.

El patrón Observador/Observable

Ya conocemos este patrón. Cuando programamos interfaces gráficas de usuario, los distintos componentes de la interfaz son Observables y nosotros escribiremos clases Observador que serán notificadas cada vez que el usuario interaccione con el componente.

El patrón Observador/Observable

  1. El usuario interacciona sobre un Componente.
  2. Se crea un Evento que describe lo que ha ocurrido.
  3. El Evento se envía a todos los Escuchadores registrados.
  4. Cada Escuchador da una respuesta.

El patrón Observador/Observable

Swing usa el modelo push de notificación.

Al informar a los Observadores, se les envía información de lo que ha ocurrido.

Esta información llega al método de notificación como una referencia en la lista de argumentos.

La información, además de datos concretos sobre lo ocurrido, describe quién fue el causante del evento, entre otros detalles.

El patrón Observador/Observable

Existe una segunda opción de notificación, en la que no se envía ninguna información de lo ocurrido, simplemente se notifica que algo ha ocurrido.

Cada uno de los Observadores es el responsable de, una vez recibida la notificación, solicitar la información que necesite.

El patrón Observador/Observable

Una ventaja del método push sobre pull es que ahorramos tiempo al no tener que hacer una nueva llamada para obtener los datos.

Otra ventaja del modelo push sobre pull es que, la información que se le envía al Observador puede ser más rica que simplemente indicar los atributos que han cambiado. Podríamos, por ejemplo, enviar también una referencia al Observado en el que se produjo el cambio. De este modo, un mismo Observador podría observar a más de un Observable al mismo tipo, y saber qué Observado cambió de estado a través de la información que se le envía en la notificación.

Como ya hemos comentado, Swing utiliza notificación push.

El patrón Observador/Observable

Una ventaja del método pull sobre push es que el objeto Observado se despreocupa completamente de los datos que le interesan al Observador.

En este caso, existe una menor dependencia entre el Observador y el Observado, ya que no se ha establecido ningún tipo de contrato a través de la lista de argumentos del método de notificación.

El patrón Observador/Observable

Tanto el esquema push como el pull están implementando el Principio de Hollywood: Tú no nos llames, ya te llamaremos nosotros.

El patrón Modelo Vista Controlador

El patrón de diseño Modelo Vista Controlador (MVC) ayuda a desacoplar nuestras clases cuando programamos interfaces gráficas de usuario.

La idea básica es agrupar las clases según tres roles:

  1. Modelo: este rol lo juegan todas las clases responsables de mantener y gestionar los datos de nuestra aplición.
  2. Vista: este rol lo juegan todas las clases responsables de visualizar datos.
  3. Controlador: conecta la Vista con el Modelo. También conectado con la dinámica de la aplicación.

El patrón Modelo Vista Controlador

Imagina que estás desarrollando una aplicación para gestionar tus contactos.

El patrón Modelo Vista Controlador

  1. La Vista es la responsable de mostrar la información gráficamente.
  2. El Modelo mantiene los datos de los contactos.
  3. El Controlador realiza cambios en el Modelo cuando el usuario interaciona con la Vista.

El patrón Modelo Vista Controlador

La dinámica que se establece entre estos tres roles es la siguiente:

El patrón Modelo Vista Controlador

La principal ventaja de este patrón es que se ha desacoplado el modelo de datos y su visualización.

Puedo cambiar el aspecto de la Vista sin necesidad de modificar el Modelo.

Si por ejemplo, hemos escrito una aplicación que muestra los datos del Modelo en forma de gráfico de dos dimensiones, podríamos reemplazar el gráfico, o añadir además, una visualización de los datos en una tabla.

Puedo, incluso, tener más de una Vista del mismo Modelo.

El patrón Modelo Vista Controlador

Puedo cambiar el Modelo sin modificar la Vista.

Los datos de mi Modelo pueden estar en un simple fichero de texto, pero en el momento en que necesite utilizar una BD, cambiaré sólo el Modelo nunca será necesario cambiar además la Vista.

Un ejemplo de aplicación con MVC

Veamos un sencillo ejemplo de implementación del patrón MVC.

Vídeo

Un ejemplo de aplicación con MVC

Modelo: Mantiene la lista de entradas. Sabe qué entrada hay seleccionada.

Vista: Interfaz gráfica, con una caja de edición de texto, tres botones y un texto de información.

Controlador: Sabe qué hay que hacer cuando el usuario pulsa cada uno de los tres botones que muestra la Vista.

Un ejemplo de aplicación con MVC

Lo que nos indica MVC es que el Modelo sólo conoce a la Vista, pero no al Controlador.

La Vista conoce al Controlador y al Modelo.

Y el Controlador conoce a la Vista y al Modelo.

Lo que significa que existen referencias cruzadas entre ellas.

Un ejemplo de aplicación con MVC

Primera decisión, no hagamos que las clases tengan referencias a otras clases (tipos concretos), si no a interfaces (tipos abstractos), de este modo desacoplamos la referencia y la implementación concreta que le inyectamos.

public class ImplementacionModelo implements Modelo {
    private Vista vista; //Vista es un interface
    ...
}
public class ImplementacionVista implements Vista {
    private Controlador controlador; //Controlador es un interface
    private Modelo modelo; //Modelo es un interface
    ...
}

Un ejemplo de aplicación con MVC

public class ImplementacionControlador implements Controlador {
    private Modelo modelo; //Modelo es un interface
    private Vista vista; //Vista es un interface
    ...
}

Tendremos que inyectar la referencia en cada uno de los atributos. Lo podremos hacer, bien con un constructor, en el momento de crear las clases concretas; bien con métodos set; o con ambos.

Un ejemplo de aplicación con MVC

El diagrama UML en este caso es:

Cada clase concreta mantiene referencias de tipo interface

Un ejemplo de aplicación con MVC

Swing utiliza el modo push para notificar a los Observadores de un componente de que algo ha ocurrido sobre él. Cada vez que el usuario interacciona con un componente gráfico, este genera un Evento que transporta información de lo que ha ocurrido.

Entre otra información, el Evento contiene una referencia al componente que lo lanzó.

Por lo tanto, si nos interesa, podemos usar el mismo Observador para más de un componente, ya que a la recepción del Evento podemos conocer el componente que lo lanzó.

Un ejemplo de aplicación con MVC

Veamos cómo implementar la dinámica del patrón MVC.

(1) Acción del usuario

Un ejemplo de aplicación con MVC

(1) Acción del usuario

class Escuchador implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        JButton boton = (JButton)e.getSource();
        String texto = boton.getText();
        if(texto.equals("Nuevo"))
            controlador.anyadeEntrada();
        else if(texto.equals("Atras"))
            controlador.atras();
        else if(texto.equals("Adelante"))
            controlador.adelante();
    }
}

Recuerda que en el patrón Observador/Observable, un Escuchador debe implementar cierto interface. En nuestros caso este interface es ActionListener.

Un ejemplo de aplicación con MVC

(1) Acción del usuario

El interface ActionListener declara un único método: public void actionPerformed(ActionEvent e).

La notificación invoca al método actionPerformed que recibe como argumento una referencia de tipo ActionEvent, quien, como ya hemos comentado, transporta información sobre lo ocurrido.

En particular, podemos conocer el componente que lanzó el evento de este modo:

e.getSource();

Un ejemplo de aplicación con MVC

(1) Acción del usuario

La Vista, notifica al Controlador de la acción del usuario:

class Escuchador implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        JButton boton = (JButton)e.getSource();
        String texto = boton.getText();
        if(texto.equals("Nuevo"))
            controlador.anyadeEntrada(); // ← Notificación
        else if(texto.equals("Atras"))
            controlador.atras(); // ← Notificación
        else if(texto.equals("Adelante"))
            controlador.adelante(); // ← Notificación
    }
}

Un ejemplo de aplicación con MVC

(2) Solicita datos

Un ejemplo de aplicación con MVC

(2) Solicita datos

public class ImplementacionControlador implements Controlador {
    ...
    public void anyadeEntrada() {
        String entrada = vista.getEntrada();
        ...
    }
    ...
}

La ventaja de que el Controlador decida los datos que necesita es que, posteriormente, si lo necesitásemos, el Controlador podría pedir nuevos datos a la Vista.

Ejemplo: en el caso de una agenda, hoy la búsqueda es por nombre, pero mañana puede ser por nombre y apellidos.

Un ejemplo de aplicación con MVC

(3) Actualiza el modelo

Un ejemplo de aplicación con MVC

(3) Actualiza el modelo

public class ImplementacionControlador implements Controlador {
    ...
    public void anyadeEntrada() {
        String entrada = vista.getEntrada();
        modelo.anyadeEntrada(entrada);
    }
    ...
}

El Controlador conoce cómo modificar el Modelo.

El Modelo se actualiza por petición del Controlador.

Un ejemplo de aplicación con MVC

(4) Notifica actualización

Un ejemplo de aplicación con MVC

(4) Notifica actualización

El Modelo notifica su cambio de estado a la Vista, por si a esta le interesa actualizarse:

public class ImplementacionModelo implements Modelo {
    ...
    public void anyadeEntrada(String entrada) {
        entradas.add(entrada);// ← El Modelo se actualiza
        posicionActual++;// ← El Modelo se actualiza
        vista.nuevaEntrada();// ← El Modelo notifica a la Vista
    }
    ...
}

Un ejemplo de aplicación con MVC

(5) Solicita nuevos datos

Un ejemplo de aplicación con MVC

(5) Solicita nuevos datos

La Vista solicita al Modelo los datos que son de su interés.

public class ImplementacionVista implements Vista {
    ...
    public void nuevaEntrada() {
        String infoEstadoEntradas = "Numero de entradas: " +
        modelo.getPoscionEntradaActual() + " de " +
        modelo.getNumeroEntradas();
        // Muestra nueva información.
    }
    ...
}

Un ejemplo de aplicación con MVC

Hemos cerrado el ciclo

Un ejemplo de aplicación con MVC

Conectémoslo todo

En nuestro método main es donde hacemos las conexiones (wiring).

public static void main(String args[]) {
	ImplementacionControlador controlador = 
	                      new ImplementacionControlador();
	ImplementacionVista vista = new ImplementacionVista();
	ImplementacionModelo modelo = new ImplementacionModelo();
	modelo.setVista(vista);
	controlador.setVista(vista);
	controlador.setModelo(modelo);
	vista.setModelo(modelo);
	vista.setControlador(controlador);
    ...
}

Un ejemplo de aplicación con MVC

Detalles de implementación

Al final, estos son los métodos declarados en los interfaces:

public interface Controlador {
    void anyadeEntrada();
    void adelante();
    void atras();
}

El Controlador declara todos los métodos que usará la Vista.

Un ejemplo de aplicación con MVC

Detalles de implementación

public interface Modelo {
    // Estos son los métodos que necesita conocer el Controlador.
    void anyadeEntrada(String entrada);
    void incrementaPosicionActual();
    void decrementaPosicionActual();
    // Y estos son los métodos que necesita concer la Vista.
    int getNumeroEntradas();
    String getEntradaActual();
    int getPoscionEntradaActual();
}

El Modelo está declarando métodos que sólo utilizará el Controlador junto a métodos que sólo utilizará la Vista.

Un ejemplo de aplicación con MVC

Detalles de implementación

public interface InformaVista {
    // Este método sólo la usa el Controlador.
    String getEntrada();
    // Estos métodos sólo los usa el Modelo.
    void entradaActualCambiada();
    void nuevaEntrada();
}

La Vista declara métodos que usan tanto el Controlador como el Modelo.

Un ejemplo de aplicación con MVC

Detalles de implementación

El interface declara métodos que algunos clientes no van a utilizar.

Imagen: http://www.tuexperto.com/wp-content/uploads/2011/10/pioneer-sc-lx85-02.jpg

Un ejemplo de aplicación con MVC

Detalles de implementación

Recuerdas la I de los Principios SOLID: El interfaz de una clase debe estar orientado a un único cliente.

En nuestro caso el interfaz de la clase ImplementacionModelo está orientado tanto al Controlador como a la Vista. Y como en la Vista tenemos:

public class ImplementacionVista implements Vista {
    private Controlador controlador; //Controlador es un interface
    ....

La Vista tiene acceso a métodos que no le interesan, y que desde el punto de vista del programador pueden resultar chocantes.

Un ejemplo de aplicación con MVC

Detalles de implementación

¿Cómo lo podemos mejorar? Sencillo, creemos dos interface a partir del anterior, en uno declaramos los métodos de interés para la Vista:

public interface InterrogaModelo {
    int getNumeroEntradas();
    String getEntradaActual();
    int getPoscionEntradaActual();
}

Un ejemplo de aplicación con MVC

Detalles de implementación

Y en el otro los métodos que interesen sólo al Controlador.

public interface CambioModelo {
    void anyadeEntrada(String entrada);
    void incrementaPosicionActual();
    void decrementaPosicionActual();
}

Un ejemplo de aplicación con MVC

Detalles de implementación

Con el interface Vista tenemos una situación parecida, declara métodos que sólo son necesarios para el Controlador y otros que sólo son necesarios para el Modelo. Podemos separarlos en dos interfaces:

public interface InterrogaVista {
    String getEntrada();
}
public interface InformaVista {
    void entradaActualCambiada();
    void nuevaEntrada();
}

Un ejemplo de aplicación con MVC

Detalles de implementación

Las clases concretas quedan como:

public class ImplementacionModelo implements CambioModelo, 
                                             InterrogaModelo {
    private InformaVista vista;
    ...
public class ImplementacionVista implements InterrogaVista, 
                                            InformaVista {
    private Controlador controlador;
    private InterrogaModelo modelo;
    ...
public class ImplementacionControlador implements Controlador {
    private InterrogaVista vista;
    private CambioModelo modelo;
    ...

Un ejemplo de aplicación con MVC

Detalles de implementación

Ahora sí que hemos acabado.

... o quizás no...

Tienes una implementación aquí ejemploMVC.zip para que sigas jugando con él.

Recursos en Internet