Comunicación entre objetos
El evento: estar a la escucha
Como sucede con los sistemas operativos orientados a eventos, sus objetos seguramente tendrán que estar a la escucha de otros objetos. Por ejemplo, su formulario gráfico estará «atento» a las acciones del ratón para poder actuar inmediatamente a las peticiones del usuario. Su formulario no sabe cuándo el usuario va a hacer clic en uno u otro componente y, por otra parte, será el propio componente el que lo llame para informarle del cambio de estado.
Un objeto que capta información la puede difundir a los «clientes», que inicialmente se hayan suscrito a su lista de difusión.
La POO siempre está muy próxima a la realidad. Se suscribe a su revista preferida como muchos otros. Los periodistas escriben artículos que se agrupan en ediciones del periódico. Regularmente, se le envía este periódico. En cualquier momento, puede detener su suscripción y suscribirse a otro medio.
En programación es lo mismo; habitualmente tendrá que implementar los mecanismos de gestión de listas de suscriptores y difusión.
El pattern Observador
1. Aspectos generales
Esta problemática era recurrente y la «cuadrilla de los cuatro» ha diseñado un «patron de diseño» (design pattern) para resolverlo.
Este pattern, llamado Observador, ofrece una solución de codificación para una relación de un (observado) respecto a varios (observadores), utilizando el acoplamiento (interdependencia) más débil posible. Esta solución se puede utilizar directamente en lenguaje Java gracias a la clase java.util.Observable y a la interfaz java.util.Observer.
Desde JDK 9, la clase java.util.Observable ha sido «obsoletizada», lo que significa que se puede continuar utilizando con este JDK, pero va a desaparecer en las versiones futuras. Evidentemente existen soluciones de sustitución, pero la simplicidad de utilización de la clase java.util.Observable hace que siga siendo la herramienta de aprendizaje ideal para establecer la comunicación entre los objetos Java.
A continuación se muestra la representación UML de este tipo de relación. Los nombres de las clases, interfaces y miembros no se corresponden con los del API Java porque es una representación genérica del design pattern.
La clase Observable y la interfaz IObservador se diseñaron a la vez, durante el desarrollo de un objeto que tenía que comunicar sus cambios de estado a sus suscriptores.
Más adelante, usted desarrolla un programa que debe, entre otras cosas, tratar las notificaciones de este objeto. Este objeto se comercializa desde hace mucho tiempo y funciona perfectamente.
La comunicación entre estos dos objetos -desarrollados en momentos diferentes- podría ser posible gracias al dúo ganador ’interfaz y polimorfismo’. En efecto, la clase Observable no conoce su objeto, pero en contraposición, su objeto puede implementar la interfaz IObservador que ha sido proporcionada por el desarrollador del objeto Observable.
Cuando su objeto se va a guardar en el Observable, no se va a declarar con su tipo nativo, sino como instancia que soporta la interfaz IObservador. Por lo tanto, incluso si Observable ignora todo de su objeto, sabe que implementa obligatoriamente un método muy particular: el que se define en IObservador. Por lo tanto, lo va a conservar como instancia de IObservador y va a poder llamar directamente a un método...
Ejercicios
1. Ejercicio 1
a. Enunciado
Este ejercicio consiste en escribir un programa que cree varias ventanas (de tipo JFrame) identificadas por un número en sus barras de título y que «trace» los eventos «presionar» y «soltar» del ratón en cada una de estas ventanas. Indicaremos cada evento mostrando en la salida por la consola de IntelliJ IDEA un mensaje que precise su naturaleza (presionar o soltar), el número de ventana y las coordenadas del puntero del ratón en el momento del evento.
Para esto, implementaremos un listener más especializado que actionListener: utilizaremos mouseListener.
b. Corrección
Cree un nuevo proyecto en IntelliJ IDEA y llámele LabMouseListener.
Añada una clase al proyecto llamada Ventana.
Haga que la clase Ventana extienda la clase JFrame (utilice el asistente IntelliJ IDEA, que va a agregar la directiva de importación del paquete, si activa [Alt][Intro] en la palabra JFrame).
Añada a la clase Ventana la implementación de la interfaz MouseListener, beneficiándose también del asistente IntelliJ IDEA, que va a agregar de golpe los cinco métodos obligatorios.
Elimine el contenido de estos cinco métodos.
Añada un constructor a la clase Ventana para:
-
Administrar la numeración única de las ventanas, gracias a un dato miembro de tipo entero static y un segundo dato miembro de tipo entero instanciado.
-
Definir las dimensiones de la ventana por defecto, gracias al método setBounds.
-
Guardarse como MouseListener gracias al método addMouseListener.
-
Definir el comportamiento de la aplicación respecto al clic en la cruz de la barra de título.
En el método mousePressed, muestre en la ventana de salida de IntelliJ IDEA el tipo de acción, el número de la ventana implicada y las coordenadas del puntero. Las coordenadas son accesibles desde el objeto MouseEvent que se pasa como argumento.
Vuelva a hacer lo mismo para el método mouseReleased.
En la clase LabMouseListener que contiene el main, instancie cuatro objetos Ventana.
A continuación se muestra el código de la clase Ventana:
package labmouselistener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame; ...
Llamadas síncronas y asíncronas
Una noción importante afecta al modo de ejecución de un método respecto al programa que utiliza. En efecto, determinados métodos pueden tomar un tiempo no despreciable durante sus ejecuciones. Esta noción de tiempo es subjetiva y depende del contexto de utilización. Por ejemplo, en aplicaciones de escritorio, una ejecución de medio segundo no es perjudicial; en el dominio industrial, no es para nada lo mismo.
El funcionamiento síncrono es el modo de ejecución por defecto. El programa que llama permanece «bloqueado» mientras dura el trabajo del método llamado.
A continuación se muestra la representación UML tipo diagrama de secuencias de una llamada síncrona desde una instancia Objeto1 hacia una instancia Objeto2.
Si la duración de ejecución se vuelve crítica, hay que ejecutar el método de manera asíncrona. En este caso, la ejecución se «divide en dos», permitiendo que las dos ramas de instrucciones evolucionen en un modo «pseudoparalelo».
A continuación se muestra la representación UML-diagrama de secuencias de una llamada asíncrona desde una instancia Objeto1 hacia una instancia Objeto2.
La programación de ejecuciones paralelas pasa por una programación de varios flujos, que vamos a presentar en el capítulo siguiente....