Download Además de escribir aplicaciones completas, Java se puede usar para
Document related concepts
no text concepts found
Transcript
COMPONENTES JAVABEANS Además de escribir aplicaciones completas, Java se puede usar para construir componentes reutilizables. Un componente es una porción de código que se utiliza para ser incorporado a una aplicación. En POO los componentes son clases compiladas. El ejemplo más simple es el del componente visual que utilizan los productos RAD, como un botón o un menú que se incluye en un formulario. En general, un componente es una cáscara, a la que el programador cliente le agrega comportamiento. Este comportamiento suele definirse en base a estímulos y las respuestas a los mismos, en consonancia con la metodología de orientación a objetos y la programación guiada por eventos que estudiaremos luego. Los componentes de Java se denominan JavaBeans, y pueden ser simples o indexados. Un componente se suele caracterizar por las propiedades que determinan su estado, los métodos que definen el comportamiento y los eventos a los cuales va a responder. Si bien en Java no hay propiedades, se pueden implementar métodos de consulta y modificación para cada una. Éste es el enfoque de los JavaBeans. Cada componente tiene una serie de propiedades, con métodos que empiezan con “get” y “set” para obtener sus valores y modificarlos. Por ejemplo, si queremos referirnos a una propiedad xxx, crearemos en nuestro componente dos métodos: getXxx y setXxx, con esa convención de mayúsculas y minúsculas. En el caso de las propiedades lógicas podemos utilizar “is” en vez de “get”. Si un componente es de sólo lectura tendrá un método “get”, pero no “set”, mientras que un componente de sólo escritura tendrá un método “set”, pero no “get”. Los métodos “get” recibirán siempre un parámetro que deberá ser del tipo de la propiedad. Los métodos de un componente son simples métodos de la clase, públicos. Además, todo componente JavaBeans debe tener un constructor sin parámetros. Todas estas convenciones, aunque parezcan caprichosas, son muy importantes porque los productos RAD y otros integradores de componentes utilizan reflexión para examinar el componente y determinar qué eventos y propiedades soporta. Para ello utilizan una clase llamada Introspector, que tiene un método de clase getBeanInfo(Class). Y éste va a buscar métodos que cumplan con la convención de nombres recién descripta. Otra forma en que actúan los productos RAD es utilizando, para un componente cualquiera de nombre Xxx, una clase XxxBeanInfo, que debe implementar la interfaz BeanInfo. Del mismo modo, si el componente utiliza eventos adicionales a los predefinidos –cosa que es perfectamente factible– se debe utilizar la nomenclatura estándar de Swing, que luego veremos en más detalle: para el evento xxxEvent debe haber una interfaz XxxListener, dos métodos, addXxxListener y removeXxxListener, etc.. A continuación se muestra el código fuente de un simple componente sin eventos: // archivo Punto.java package carlosfontela.geometria; 2 public class Punto { private double coordX; private double coordY; public Punto (double x, double y) { coordX = x; coordY = y; } // propiedad x, de tipo double, de lectura y escritura public double getX () { return coordX; } public void setX (double nuevoX) { coordX = nuevoX; } // propiedad y, de tipo double, de lectura y escritura public double getY () { return coordY; } public void setY (double nuevoY) { coordY = nuevoY; } public void trasladar (double deltaX, double deltaY) { coordX += deltaX; coordY += deltaY; } public double distancia (Punto otro) { return Math.sqrt ( Math.pow((coordX-otro.coordX),2) + Math.pow((coordY-otro.coordY),2) ); } } Como se ve, hemos convertido a nuestra clase Punto en un componente estándar con sólo agregar los “get” y “set” correspondientes. Las propiedades de la clase se llaman, entonces, x e y, y los métodos son trasladar y distancia. Los JavaBeans deben ser incluidos en un archivo JAR, que contiene un manifiesto para indicar que se trata de un JavaBeans y dar a conocer el nombre de la clase y del paquete respectivos. Para funcionar correctamente, el archivo JAR debe tener todo el directorio (paquete) con las clases necesarias. En general las herramientas para generar componentes se ocupan de todos los detalles y generan un manifiesto por nosotros. De todas maneras, aquí mostramos un manifiesto de ejemplo: // Punto.mf Manifest-Version: 1.0 Name: carlosfontela.geometria/Punto.class Java-Bean: True 3 Recordemos también que, como un componente puede ser utilizado en un contexto concurrente, deben sincronizarse todos sus métodos públicos. Es cierto que esto puede dar lugar a deficiencias en el desempeño, pero cualquier optimización en este sentido deberá hacerse solamente si se comprueba que la sincronización no es necesaria, además de demostrarse que la supuesta ineficiencia es tal y que es eliminada al suprimir la sincronización. Los componentes JavaBeans pueden ser usados desde páginas JSP, mediante la etiqueta jsp:usebean, que se ocupa de crear una nueva instancia del componente si no existiera. Por ejemplo, observemos el siguiente código JSP: <% CarroCompras c = (CarroCompras)session.getAttribute("carro"); // si no hay carro de compras, creamos uno: if (c == null) { c = new CarroCompras(); session.setAttribute("carro",c); } %> El código recién escrito puede reducirse bastante si el carro de compras fuera un componente JavaBeans: <jsp:usebean id="carro" class="carlosfontela.utilidades.ecommerce.CarroCompras" scope="session" /> Notemos que se le puede definir un alcance de sesión, aplicación, etc., usando el atributo scope. Igualmente se puede incluir una etiqueta jsp:setProperty, para inicializar propiedades del componente, y una jsp:getProperty para obtener su valor. Veremos brevemente el lenguaje JSP en el próximo capítulo. PROGRAMACIÓN GUIADA POR EVENTOS La programación secuencial y sus limitaciones La modalidad de programación que se usaba antes de la llegada de los ambientes de interfaz de usuario gráfica, desde el punto de vista de la interacción del usuario con el programa, es llamada programación secuencial. En este paradigma, el programa tiene el control de lo que pasa e interactúa con el usuario pidiéndole datos solamente. Se puede decir que es el usuario quien espera al programa, ya que solamente podemos decirle lo que queremos cuando éste nos lo pregunta explícitamente. La arquitectura típica de esta forma de interacción es un programa que opera según un diagrama de actividades similar al siguiente: 4 Leer comando o valor Análisis del dato Proceso y generación de resultado [seguir] Salida hacia el usuario [terminar] Ejemplos de esta forma de interacción son los sistemas operativos de línea de comandos, como DOS y Unix, y todas las aplicaciones que se desarrollaban en esos entornos. Pero aun en esos ambientes fue surgiendo la necesidad de mejorar la interacción con el usuario, de modo de darle la impresión de que es él quien maneja la secuencia de ejecución. Así apareció el concepto de menú, en el cual, si bien no se altera la forma de trabajo que hemos descripto, se presentan al usuario una serie de opciones de las cuales éste elige una, y la computadora realiza una u otra acción en respuesta. A continuación se muestra el diagrama de actividades de una interfaz con menúes: 5 Menú [opción 3] [opción 1] [opción 2] Análisis del dato Análisis del dato Análisis del dato [seguir] Proceso y generación de resultado Proceso y generación de resultado Proceso y generación de resultado Salida hacia el usuario Salida hacia el usuario Salida hacia el usuario [terminar] Con el tiempo, se fue dejando de lado la idea de un único menú en cada pantalla y se pasó a tener menúes desplegables con un gran número de opciones. Eso hicieron los programas típicos de procesamiento de texto y planilla de cálculo. Pero como mientras se estaba editando un documento no se podía trabajar simultáneamente con un menú, se implementó el concepto de modo, de forma tal que el usuario podía cambiar, por ejemplo, del “modo de edición” al “modo de menú” o viceversa, pulsando una tecla. Wordstar y Visicalc fueron los más conocidos entre los precursores de esta forma de interacción. Pero notemos que en estos casos las opciones que tiene el usuario siempre están limitadas a las que la aplicación le muestra en pantalla. ¿Cómo haríamos con este esquema para prever las múltiples acciones que puede hacer el usuario en un ambiente WIMP? Observemos que en una interfaz gráfica el usuario puede cerrar una ventana, minimizarla o maximizarla, moverla, seleccionar cualquier botón, seleccionar algún menú, hacer clic con el mouse en algún gráfico o elemento que se ve en la pantalla, escribir algo con el teclado en un cuadro de texto, etc., todo esto sin un orden prefijado por el programador de la aplicación. Incluso el usuario posee múltiples caminos para realizar una misma acción. La noción de entrada y salida viene asociada ya no tanto a lo que se ingresa por teclado o se informa en pantalla, sino al almacenamiento de datos en medios permanentes o la impresión en papel. Eventos y programación Al estudiar los diagramas de estados de UML dijimos que un evento indica la aparición de un estímulo que puede disparar una transición de estados. Es la especificación de un acontecimiento significativo, como una señal recibida, un cambio de 6 estado o el simple paso de un intervalo de tiempo. La programación guiada por eventos (cuyo nombre viene de la expresión inglesa “event driven programming”) es la modalidad de interacción que se impuso con las interfaces de usuario WIMP desde el punto de vista del programador. De todas maneras, no es exclusiva de éstas. La idea de la programación por eventos, expresada de un modo simple es que, en lugar de que el usuario espere al programa, ponemos al programa a esperar al usuario. Ahora el sistema espera la ocurrencia de eventos y actúa en consecuencia. Es decir, el programa debe saber cómo manejar los objetos disponibles y responder a los estímulos que vienen del usuario. En la programación guiada por eventos, no es que las operaciones se produzcan porque el programa llegó a una determinada fase de ejecución, sino que las provoca un usuario o un actor externo mediante un evento. Un evento, en este contexto, es algo interesante que pasa en el sistema. Podría decirse que un evento es una condición o acción que es observada por el sistema pero ocurre fuera del control del mismo. Observamos que esta definición no invalida la anterior, sino que la complementa. Los eventos son previstos, pero no planeados. Esto es, se conoce el hecho de que el evento puede ocurrir, pero no se conocen las circunstancias o el momento en que ocurrirá. A continuación se muestra como ejemplo el diagrama de estados de una aplicación simple utilizando programación por eventos: Inicio aplicación [Aplicación iniciada] Esperando eventos [Nuevo evento] [Evento procesado] Procesando eventos [Evento de terminación] Estas aplicaciones se inician creando la pantalla principal e iniciando la cola de eventos. El estado casi permanente de la aplicación es estar inactiva, esperando la ocurrencia de eventos. Los eventos son procesados en orden, obteniéndolos de la cola de eventos, y el proceso de cada evento implica despacharlo al componente correcto. Los eventos pueden ser provocados por el usuario, cuando es éste quien los 7 dispara, o por el sistema o el medio. Ejemplos de eventos de usuario pueden ser: Un clic del mouse. • La pulsación de una tecla. • Una selección en un menú. • Un movimiento del mouse. • La toma o pérdida de foco. • Ejemplos de eventos del sistema o del medio pueden ser: • Los intervalos del reloj. • El paso del tiempo. • El cierre del sistema. • Un mensaje que proviene de otra aplicación. • Un mensaje que proviene de otra computadora. La POO es muy importante para soportar la programación por eventos. Cada componente que recibe un estímulo es un objeto, y este objeto va a responder al mensaje con la ejecución de un método definido en su clase. Por ejemplo, cuando un usuario pulsa sobre un botón, está provocando un evento que envía un mensaje a dicho botón. A su vez esto provoca que éste reaccione con un método definido en su clase. Eventos en Swing ¿Cómo manejamos el evento? Es decir, ¿cómo llamamos al código que debe procesar el evento? Cuando se hace uso de callbacks, como en el caso de Swing, existe una entidad que contiene el código de manejo para los eventos. Cada componente registra qué código callback se debe ejecutar cuando recibe un evento. Así, hay un controlador que está permanentemente escuchando para ver si se producen eventos. Cuando recibe uno, llama al método que corresponda en el componente que se suscribió para recibirlo. En consecuencia, un evento es también un vínculo entre una ocurrencia en el sistema y una porción de código que responde a esa ocurrencia. Esa porción de código es lo que denominamos un manejador de eventos. Ya hemos dicho que cada componente Swing (un objeto instancia de una clase heredera de JComponent) puede disparar un evento. Este evento puede ser escuchado por uno o más observadores1 (en inglés, “listeners”), que actúan en base a lo que el programador haya indicado. De este modo, el evento se lanza en un lugar (el componente) y puede ser manejado en un lugar diferente (el observador). Cada observador es una instancia de una clase que implementa una interfaz observadora. Llamamos interfaz observadora a cualquiera que sea descendiente de EventListener, como ActionListener, KeyListener, etc.. Los eventos son instancias de clases descendientes de EventObject, como ActionEvent, ItemEvent, etc.. A continuación se muestra una tabla que clarifica un poco estos conceptos: 1 La palabra “oyente”, que se usa en general, no me parece adecuada porque da una idea de pasividad. Además, “observador” está más en consonancia con el patrón Observador, en el cual se basa el manejo de eventos de Swing. 8 Significado Hereda de Ejemplos Otro nombre Componente Clase capaz de recibir y emitir estímulos JComponent JButton, JLabel, JMenu, etc. Emisor de eventos Observador Clase que está dispuesta a recibir eventos interfaz ActionListener, EventListener FocusListener, etc. Evento Cualquier cosa EventObject interesante que ocurre en el sistema KeyEvent, MouseEvent Suscriptor Estímulo Véase este ejemplo: import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Hola3 extends JApplet { JButton boton = new JButton("Presione"); JTextField texto = new JTextField(4); class BotonHola implements ActionListener { public void actionPerformed(ActionEvent e) { texto.setText(“Hola a todos”); } } BotonHola observador = new BotonHola(); public void init() { boton.addActionListener(observador); Container cp = getContentPane(); cp.setLayout(new FlowLayout()); cp.add(boton); cp.add(texto); } } Notemos que la clase BotonHola implementa la interfaz observadora ActionListener. Por lo tanto, es un objeto de la clase BotonHola el observador que va a manejar el evento. Toda la lógica de manejo del evento va en la clase observadora. Así lo hicimos al escribir: class BotonHola implements ActionListener { public void actionPerformed(ActionEvent e) { texto.setText(“Hola a todos”); } } En general las clases observadoras se definen como internas al componente, 9 porque es lógico acoplarlas con las clases a las cuales sirven. De todas formas, se pueden mantener ambas clases separadas. Esto es útil para permitir que el programador personalice el comportamiento de componentes visuales sin tener que cambiar las propias clases. Otra forma habitual de trabajar es mediante clases internas anónimas. En nuestro ejemplo, la clase BotonHola sólo sirvió para definir el método actionPerformed. A continuación se escribe el applet completa con una clase interna anónima descendiente de JButton, lo que nos ahorra la definición de BotonHola: import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Hola5 extends JApplet { JButton boton = new JButton("Presione"); JTextField texto = new JTextField(4); // la clase BotonHola no está más: ActionListener observador = new ActionListener() { public void actionPerformed(ActionEvent e) { texto.setText(“Hola a todos”); } }; public void init() { boton.addActionListener(observador); Container cp = getContentPane(); cp.setLayout(new FlowLayout()); cp.add(boton); cp.add(texto); } } Pero el lanzamiento del evento, representado aquí por un objeto de la clase ActionEvent, se hace desde la clase Hola5. Como acabamos de decir, se pueden añadir más observadores que respondan a un determinado evento. Por eso decimos que cada componente Swing puede reportar todos los eventos individualmente, y es el programador quien registra su interés en uno en particular, o simplemente lo ignora. Todos los componentes de Swing tienen dos métodos para cada tipo de evento que pueden lanzar: addXxxListener y removeXxxListener, donde Xxx es el nombre de la interfaz. El primero permite registrar el interés en un evento y el segundo declarar que el mismo ya no interesa más. Por lo tanto, la suscripción puede manejarse dinámicamente. En líneas generales, lo que hace un programador al que le interesa un evento es crear un objeto observador y registrarlo con el componente que va a lanzar dicho evento. Eso hicimos en el ejemplo: boton.addActionListener(observador); Descomponiendo el proceso en pasos, la idea es: 1. Agregar la palabra Listener al nombre de evento, y ésta será la interfaz 10 observadora a implementar (en la clase interna). 2. Implementar la interfaz y escribir los métodos para los eventos que se quieren capturar. 3. Crear un objeto de la clase observadora y registrarlo en el componente con el prefijo add frente al nombre de la misma. Observamos que en este modelo de eventos se trabaja con el patrón Observador, pues hay un objeto que actúa en función de los cambios que se hacen sobre alguna parte del sistema. El sistema funciona como objeto “observado”, y el observador maneja los eventos reaccionando con un comportamiento determinado. Como dijimos, cada componente puede lanzar uno o más eventos. Pero cada tipo de componente soporta sólo ciertos tipos de eventos. Si se intenta capturar un evento que no existe para un determinado componente, se provocará un error de compilación. A continuación hay una lista de algunos eventos, interfaces observadoras y métodos que se usan2: Evento Significado Interfaz observadora Métodos de la interfaz ActionEvent Un botón presionado y, en general, cualquier elección ActionListener actionPerformed (ActionEvent) AdjustmentEvent Cualquier acción que implique ajustar posición AdjustmentListener adjustmentValueChanged (AdjustmentEvent) FocusEvent Toma de foco FocusListener por el componente focusGained (FocusEvent), focusLost (FocusEvent) KeyEvent Presión de una tecla keyPressed (KeyEvent), keyReleased (KeyEvent), keyTyped (KeyEvent) MouseEvent Clic de mouse MouseListener mouseClicked (MouseEvent), mouseEntered(MouseEvent), mouseExited (MouseEvent), mousePressed (MouseEvent), mouseReleased (MouseEvent) MouseEvent Movimiento de mouse mouseDragged (MouseEvent), mouseMoved(MouseEvent) 2 KeyListener MouseMotionListener Como se ve, los nombres son bastante descriptivos. 11 Evento Significado Interfaz observadora WindowEvent Cambios de WindowListener estado en la ventana en cuanto a activarla, agrandarla, achicarla, etc. windowOpened(WindowEvent), windowClosing(WindowEvent), windowClosed(WindowEvent), windowActivated (WindowEvent), windowDeactivated (WindowEvent), windowIconified (WindowEvent), windowDeiconified (WindowEvent) ItemEvent Elección de ItemListener un ítem, como una opción en un botón de radio, en una lista, etc. itemStateChanged(ItemEvent) TextEvent Escritura de un texto addTextListener, removeTextListener TextListener Métodos de la interfaz Ya hemos dicho que no cualquier componente dispara cualquier evento. Por ejemplo, un JButton puede disparar un ActionEvent pero no un WindowEvent; de la misma forma, JCheckBox disparará un ItemEvent, pero no un AdjustmentEvent. Como hay interfaces que tienen muchos métodos, y a veces no vamos a querer implementar todos, Swing nos provee adaptadores de interfaz3, que son clases para las cuales ya existen los métodos implementados, generalmente vacíos. De esa manera, se pueden delegar las implementaciones que no se quieran hacer, heredando de la clase adaptadora. De todas maneras, no todas las interfaces tienen clases adaptadoras. A continuación se muestra un pequeño programa que responde a tres tipos de eventos para el mismo botón: import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Hola6 extends JApplet { BotonHola boton = new BotonHola("Presione"); JTextField texto = new JTextField(4); // clase interna a Hola6: class BotonHola extends JButton { public BotonHola (String leyenda) { super(leyenda); setBackground(Color.green); addFocusListener(f); addMouseListener(m); 3 Como WindowAdapter, MouseAdapter, etc.. 12 } // un atributo de una clase interna anónima: FocusListener f = new FocusListener() { public void focusGained(FocusEvent e) { boton.setBackground(Color.red); } public void focusLost(FocusEvent e) { boton.setBackground(Color.green); } }; // un atributo de una clase interna anónima: MouseListener m = new MouseListener() { public void mouseEntered(MouseEvent e) { boton.setBackground(Color.red); } public void mouseExited(MouseEvent e) { boton.setBackground(Color.green); } public void mousePressed(MouseEvent e) { texto.setText(“Hola a todos”); } public void mouseReleased(MouseEvent e) { boton.setBackground(Color.green); } public void mouseClicked(MouseEvent e) { texto.setText(“Hola a todos”); } }; } public void init() { Container cp = getContentPane(); cp.setLayout(new FlowLayout()); cp.add(boton); cp.add(texto); } } Generalización del concepto de eventos El concepto de evento está tan asociado a las interfaces de usuario gráficas que todas las implementaciones RAD permiten usar eventos predefinidos para los componentes visuales. Sin embargo, como un lenguaje orientado a objetos debiera promover la reutilización y extensión, los auténticos lenguajes de POO deberían poder definir nuevos eventos, incluso sin estar asociados a un componente visual. Esto es lo que ocurre en la biblioteca Swing de Java. La idea de una generalización del concepto de evento es extenderlo a cualquier sistema, no sólo al entorno visual y la interacción con el usuario. De este modo, por ejemplo, podríamos denominar eventos a los siguientes escenarios: • El vuelo AR1023 ha despegado. 13 • • Se ha detectado humo en el sensor número 2. Se ha retirado un objeto del arreglo v. Ocurre que el concepto de evento no está limitado a la representación de cambios debidos a acciones directas del usuario. La programación guiada por eventos responde al paradigma de suscriptor y distribuidor, por lo que todo problema al que le sea aplicable dicho paradigma se podrá implementar con eventos. En el paradigma de suscriptor y distribuidor hay un objeto controlador que se ocupa de escuchar eventos de distintas fuentes y enviarlos a los suscriptores interesados. Para que el controlador sepa qué objetos están interesados en cada evento, cada observador se tiene que suscribir o registrar para indicar qué eventos le interesan. Una vez que se produce el evento, el controlador que estaba escuchando llama a los métodos (esto se conoce como callback) que correspondan en los objetos interesados. Java sólo permite definir eventos para componentes. Sin embargo, esto no es una limitación, sino que habrá que hacer componentes de nuestras clases generadoras de eventos. Las propiedades de los componentes JavaBeans pueden notificar a otros objetos el hecho de que han cambiado. Ante un cambio, lanzan un evento de la clase PropertyChangeEvent, que puede ser capturado por un objeto que implemente la interfaz PropertyChangeListener. Por lo tanto, para poder implementar este modelo, habrá que: • Tener un componente JavaBeans cuyos cambios de propiedades interese notificar. • Implementar objetos que registren su interés en recibir los eventos enviados por los cambios en propiedades del componente. • Disparar los eventos de cambio de propiedades en el componente hacia los objetos observadores. El componente debe implementar los métodos addPropertyChangeListener, removePropertyChangeListener y firePropertyChange. Para la implementación de los dos primeros, podemos delegar la tarea en la clase predefinida PropertyChangeSupport, que se ocupa de mantener la lista de observadores. En este último caso, debemos incluir un objeto de esta clase como atributo, que además nos va a servir para que se utilice el método firePropertyChange. Por su lado, los observadores deben implementar la interfaz PropertyChangeListener, que expone el método propertyChange, y utilizarlo para programar la acción a seguir cuando se recibe el evento. Incluso existe la posibilidad de que un objeto decida evitar un cambio en una propiedad, elevando una excepción PropertyVetoException, que provoca que la propiedad vuelva al estado inicial. Al final del capítulo hicimos un ejercicio que usa estas facilidades. HILOS DE CIERRE Una aplicación Java puede terminar por muchas razones. La más simple es la salida normal que se produce una vez que terminó de ejecutarse el último hilo. Otra posibilidad es la llamada al método System.exit desde algún punto del programa. Pero 14 también puede terminar por decisión del usuario, que la puede interrumpir bruscamente, por una excepción no capturada u otras finalizaciones menos ortodoxas. Ahora bien, en cualquiera de estos casos, la terminación de la aplicación implica la finalización de una instancia de la JVM. Por eso, se pueden definir ciertos hilos que se ejecuten antes de terminar con dicha instancia, lo que hará más segura la terminación de un programa: se podrán cerrar conexiones de red, vínculos con bases de datos, persistir algunos objetos, etc.. Hay dos métodos para administrar estos hilos: Runtime.addShutdownHook y Runtime.removeShutdownHook, ambos con un parámetro de tipo Thread. EJERCICIO: EVENTOS DEFINIDOS POR EL PROGRAMADOR Enunciado: Implementar un simulador de horno a microondas trabajando con eventos de notificación de cambios. Implementar también una clase de alarma, que responda al evento de finalización de la cocción. Solución: // archivo Microondas2.java package carlosfontela.varios; import java.util.*; import java.beans.*; public class Microondas2 extends Observable { private int tiempo; // en segundos private int tiempoRestante; // en segundos private int potencia; // en Watt private boolean andando; private long horaInicio; // en milisegundos desde 1/1/1970 private Timer t; private ObservacionTiempo observacionTiempo; private PropertyChangeSupport cambio; // una clase interna derivada de TimerTask: class ObservacionTiempo extends TimerTask { public void run() { // tiempo en ms desde 1/1/1970: long horaActual = System.currentTimeMillis(); long falta = horaActual - horaInicio; if (falta > 0) tiempoRestante = (int)(falta / 1000); else { andando = false; tiempoRestante = 0; observacionTiempo.cancel(); setChanged(); notifyObservers(); cambio.firePropertyChange("andando", new Boolean(true), new Boolean(false)); 15 } } } public Microondas2() { tiempo = 0; potencia = 0; andando = false; horaInicio = 0; t = new Timer(); cambio = new PropertyChangeSupport(this); } public int getTiempo() { return tiempo; } public int getPotencia() { return potencia; } public void setTiempo (int t) { tiempo = t; } public void setPotencia (int p) { potencia = p; } public void incrementarPotencia() { potencia += 100; } public void decrementarPotencia() { potencia -= 100; } public void incrementarTiempo() { tiempo += 30; } public void decrementarTiempo() { tiempo -= 30; } public void inicioCoccion() { andando = true; horaInicio = System.currentTimeMillis(); // tiempo en ms desde 1/1/1970 observacionTiempo = new ObservacionTiempo(); t.scheduleAtFixedRate(observacionTiempo,0,1000); } public int getTiempoRestante() { return tiempoRestante; } public void detenerCoccion() { 16 } observacionTiempo.cancel(); andando = false; tiempoRestante = 0; public boolean isAndando() { return andando; } public void addPropertyChangeListener(PropertyChangeListener o) { cambio.addPropertyChangeListener(o); } public void removePropertyChangeListener(PropertyChangeListener o) { cambio.removePropertyChangeListener(o); } } // archivo Alarma.java package carlosfontela.varios; import java.beans.*; import java.util.*; import carlosfontela.basico.Beep; public class Alarma implements PropertyChangeListener { private Microondas2 observado; public void pitido() { new Beep(2); } public void setObservado(Microondas2 obs) { observado = obs; observado.addPropertyChangeListener(this); } public Microondas2 getObservado() { return observado; } public void propertyChange (PropertyChangeEvent e) { pitido(); } }