Propiedades ligadas

prev.gif (997 bytes)chapter.gif (1105 bytes)home.gif (1232 bytes)next.gif (1211 bytes)

JavaBeans

Una clase con una propiedad ligada (bound)

La clase que define un suceso

El interface XXXListener

La fuente de los sucesos (events)

Los objetos (listeners) interesados

Vinculación entre la fuente de sucesos y los objetos (listeners) interesados

El código fuente


Cuando a un empleado (objeto de la clase Asalariado) le aumentan el sueldo, ha de notificar este hecho a otros: la familia, la Hacienda pública, etc. Vamos a estudiar con detalle los pasos necesarios para crear una clase Asalariado con una propiedad ligada (el sueldo) y otra clase Hacienda cuyos objetos (funcionarios) están interesados en el cambio en el valor de dicha propiedad.

Este ejemplo, nos permitirá profundizar aún más en el mecanismo que emplea Java 1.1 para responder a las acciones del usuario sobre los distintos controles y grupos de controles.

 

Una clase con una propiedad ligada (bound)

Creamos una clase denominada Asalariado con una propiedad ligada (bound) denominada sueldo de tipo int.

public class Asalariado{
  private int sueldo;
  
  public Asalariado() {
    sueldo=20;
  }
  public void setSueldo(int nuevoSueldo){
    sueldo=nuevoSueldo;
  }
  public int getSalario(){
    return sueldo;
  }
//...
}

La clase Asalariado tiene un constructor por defecto, que asigna un valor inicial de 20 al miembro dato sueldo. Sueldo es una propiedad ya que tiene asociados dos métodos que empiezan por set y get.

Para notificar un cambio en dicha propiedad necesitamos llevar a cabo las siguientes tareas:

  1. Crear una clase que defina un suceso (event) personalizado, denominada XXXEvent
  1. Crear un interface denominado XXXListener, que declare los métodos que los objetos (listeners) a los que se le notifican el cambio en dicha propiedad, precisen implementar.
  1. Crear un array (vector) que contenga la lista de objetos (listeners) interesados en el cambio en el valor de dicha propiedad.
  1. Definir dos funciones denominadas addXXXListener y removeXXXListener, que añadan o eliminen objetos de dicha lista.

Vamos a estudiar detalladamente cada uno de los pasos:

 

La clase que define un suceso

Un suceso (event) es un objeto que indica que algo ha sucedido. Puede ser que el usuario haya movido el ratón, que un paquete de datos haya llegado a través de la red, etc. Cuando algo sucede, se ha de realizar alguna acción, por ejemplo, dibujar en la superficie del applet cuando se mueve el ratón, imprimir en la pantalla la información que ha llegado, etc.

La clase que define nuestro suceso (event) personalizado, que denominamos SalarioEvent, deriva de EventObject. Dicha clase tiene dos miembros dato, el sueldo que cobraba antes anteSueldo, y el sueldo que cobra ahora, nuevoSueldo. La clase base EventObject precisa conocer la fuente de los sucesos, que se le pasa en el primer parámetro del constructor luego, inicializa los dos miembros dato, que se pueden declarar private o protected, según convenga.

La clase define dos funciones miembro, getNuevoSueldo y getAnteSueldo, que permiten conocer los valores que guardan los dos miembros dato.

import java.util.*;

public class SalarioEvent extends EventObject {
  protected int anteSueldo, nuevoSueldo;
  public SalarioEvent(Object fuente, int anterior, int nuevo) {
    super(fuente);
    nuevoSueldo=nuevo;
    anteSueldo=anterior;
  }
  public int getNuevoSueldo(){ return nuevoSueldo;}
  public int getAnteSueldo(){ return anteSueldo;}
}

 

El interface XXXListener

Un interface es un grupo de métodos que implementan varias clases independientemente de su relación jerárquica, es decir, de que estén o no en una jerarquía.

La clase cuyos objetos (listeners) están interesados en el cambio en el valor de la propiedad ligada, ha de implementar un interface que se ha denominado SalarioListener. Dicho interface declara una única función enteradoCambioSueldo que ha de ser definida por la clase que implemente el interface.

import java.util.*;

public interface SalarioListener extends EventListener {
  public void enteradoCambioSueldo(EventObject e);
}

 

La fuente de los sucesos (events)

Un objeto que está interesado en recibir sucesos (events) se denomina event listener. Un objeto que que produce los sucesos se llama event source, el cual mantiene una lista salarioListeners (objeto de la clase Vector) de objetos que están interesados en recibir sucesos y proporciona dos métodos para añadir addSalarioListener o eliminar removeSalarioListener dichos objetos de la lista.

public class Asalariado{
  private Vector salarioListeners=new Vector();

  public synchronized void addSalarioListener(SalarioListener listener){
        salarioListeners.addElement(listener);
  }

  public synchronized void removeSalarioListener(SalarioListener listener){
        salarioListeners.removeElement(listener);
  }
//...
}

Cada vez que se produce un cambio en el valor de la propiedad Sueldo, se ha de notificar dicho cambio a los objetos interesados que se guardan en el vector salarioListeners.

La función miembro o método que cambia la propiedad se denomina setSueldo. La tarea de dicha función como hemos visto anteriormente es la de actualizar el miembro dato sueldo, pero también tiene otras tareas como son las de crear un objeto de la clase SalarioEvent y notificar a los objetos interesados (listeners) de dicho cambio llamando a la función miembro notificarCambio y pasándole en su único argumento el objeto event creado.

Para crear un objeto event de la clase SalarioEvent, se precisa pasar al constructor tres datos: el objeto fuente de los sucesos, this, el sueldo que cobraba antes, anteSueldo y el sueldo que cobra ahora, nuevoSueldo.

  public void setSueldo(int nuevoSueldo){
    int anteSueldo=sueldo;
    sueldo=nuevoSueldo;
    if(anteSueldo!=nuevoSueldo){
        SalarioEvent event=new SalarioEvent(this, anteSueldo, nuevoSueldo);
        notificarCambio(event);
    }    
  }

Se define la función notificarCambio, para notificar el cambio en la propiedad Sueldo a los objetos (listeners) que están interesados en cambio de dicha propiedad y que se guardan en el vector salarioListeners,. En dicha función, se crea una copia del vector salarioListeners y se guarda en la variable local lista de la clase Vector. La palabra clave synchronized evita que varios  procesos ligeros o threads puedan acceder simultáneamente a la misma lista mientras se efectúa el proceso de copia.

    Vector lista;
    synchronized(this){
        lista=(Vector)salarioListeners.clone();
    }

Finalmente, todos los objetos (listeners) interesados y que se guardan en el objeto lista, llaman a la función miembro enteradoCambioSueldo, ya que la clase que describe a dichos objetos, como veremos más adelante, implementa el interface SalarioListener. En el estudio de la clase Vector vimos como se accedía a cada uno de sus elementos.

    for(int i=0; i<lista.size(); i++){
        SalarioListener listener=(SalarioListener)lista.elementAt(i);
        listener.enteradoCambioSueldo(event);
    }

El código completo de la clase Asalariado es el siguiente

import java.beans.*;
import java.util.*;

public class Asalariado{
  private Vector salarioListeners=new Vector();
  private int sueldo;

  public Asalariado() {
    sueldo=20;
  }

  public void setSueldo(int nuevoSueldo){
    int anteSueldo=sueldo;
    sueldo=nuevoSueldo;
    if(anteSueldo!=nuevoSueldo){
        SalarioEvent event=new SalarioEvent(this, anteSueldo, nuevoSueldo);
        notificarCambio(event);
    }
  }
  public int getSalario(){
    return sueldo;
  }

  public synchronized void addSalarioListener(SalarioListener listener){
        salarioListeners.addElement(listener);
  }
  public synchronized void removeSalarioListener(SalarioListener listener){
        salarioListeners.removeElement(listener);
  }

  private void notificarCambio(SalarioEvent event){
    Vector lista;
    synchronized(this){
        lista=(Vector)salarioListeners.clone();
    }
    for(int i=0; i<lista.size(); i++){
        SalarioListener listener=(SalarioListener)lista.elementAt(i);
        listener.enteradoCambioSueldo(event);
    }
  }
}

 

Los objetos (listeners) interesados

La clase Hacienda que describe los objetos que están interesados en el cambio en el valor de la propiedad Sueldo, han de implementar el interface SalarioListener y definir la función enteradoCambioSueldo. Definimos una clase denominada Hacienda cuyos objetos (los funcionarios inspectores de hacienda) están interesados en el cambio de sueldo de los empleados.

public class Hacienda implements SalarioListener{

  public Hacienda() {
  }
  public void enteradoCambioSueldo(EventObject ev){
    if(ev instanceof SalarioEvent){
        SalarioEvent event=(SalarioEvent)ev;
        System.out.println("Hacienda: nuevo sueldo    "+event.getNuevoSueldo());
        System.out.println("Hacienda: sueldo anterior "+event.getAnteSueldo());
    }
  }
}

Como la función enteradoCambioSueldo recibe un objeto ev de la clase SalarioEvent, podemos extraer mediante las funciones miembro que se definen en dicha clase toda la información relativa al suceso: el sueldo que cobraba antes el empleado y el sueldo que cobra ahora. Esta información la obtenemos llamando a las funciones miembro getNuevoSueldo y getAnteSueldo. Con esta información el funcionario de Hacienda puede calcular las nuevas retenciones, impuestos, etc. En este caso, se limita, afortunadamente, a mostrar en la pantalla el sueldo anterior y el nuevo sueldo.

 

Vinculación entre la fuente de sucesos y los objetos (listeners) interesados

Para probar las clases Asalariado y Hacienda y comprobar como un objeto de la primera clase notifica el cambio en el valor de una de sus propiedades a un objeto de la segunda clase, creamos una aplicación.

En dicha aplicación, se crean dos objetos uno de cada una de las clases, llamando a su constructor por defecto o explícito según se requiera.

    Hacienda funcionario1=new Hacienda();
    Asalariado empleado=new Asalariado();

La vinculación entre el objeto fuente, empleado, y el objeto interesado en conocer el cambio en el valor de una de sus propiedades, funcionario1 se realiza mediante la siguiente sentencia.

    empleado.addSalarioListener(funcionario1); 

El objeto funcionario1 se añade a la lista (vector) de objetos interesados en conocer el nuevo sueldo del empleado. Podemos poner más sentencias similares, para que más funcionarios de Hacienda sean notificados de dicho cambio. También podemos crear otra clase que se llame por ejemplo Familia, que implemente el interface SalarioListener y defina la función enteradoCambioSueldo. Creamos objetos de esta clase, la mujer, los hijos, etc, y los añadimos mediante addSalarioListener a la lista de personas (listeners) interesados en conocer la noticia.

Cuando escribimos la sentencia

    empleado.setSueldo(50);
  1. El objeto empleado llama a la función  setSueldo que cambia la propiedad.
  1. En el cuerpo de setSueldo, se comprueba que hay un cambio en el valor del miembro dato sueldo.
  1. Se llama a la función miembro notificarCambio para dar a conocer a los objetos interesados que se guardan en el vector salarioListeners el cambio en el valor de dicha propiedad.
  1. En el cuerpo de notificarCambio, los objetos (listeners) interesados llaman a la función enteradoCambioSueldo.
  1. Los objetos interesados que pueden ser de la misma clase o de distinta clase, siempre que implementen el inteface SalarioListener, realizan en el cuerpo de la función enteradoCambioSueldo las tareas, no siempre oportunas, con la información que se le proporciona a través del objeto ev de la clase SalarioEvent.

El código completo de la aplicación es el siguiente:

public class EjemploApp {

  public static void main(String[] args) {
    Hacienda funcionario1=new Hacienda();
    Asalariado empleado=new Asalariado();
    System.out.println("----------------------------");
    empleado.addSalarioListener(funcionario1);
    empleado.setSueldo(50);
}

 

El código fuente

disco.gif (1035 bytes)Asalariado.java, SalarioEvent.java, SalarioListener.java, Hacienda.java, EjemploApp.java