2010-05-12 9 views
11

Estoy usando un framework llamado Processing que es básicamente un applet de Java. Tiene la capacidad de hacer eventos clave porque Applet puede. También puede pasar sus propias devoluciones de llamada al padre. No estoy haciendo eso ahora y tal vez esa es la solución. Por ahora, estoy buscando una solución más POJO. Entonces escribí algunos ejemplos para ilustrar mi pregunta.Viendo una variable para cambios sin sondeo

Ignore el uso de eventos clave en la línea de comandos (consola). Sin duda, esta sería una solución muy limpia, pero no es posible en la línea de comandos y mi aplicación real no es una aplicación de línea de comandos. De hecho, un evento clave sería una buena solución para mí, pero estoy tratando de comprender los eventos y las encuestas más allá de los problemas específicos del teclado.

Ambos ejemplos dan la vuelta a un booleano. Cuando el booleano se voltea, quiero disparar algo una vez. Podría envolver el booleano en un Objeto así que si el Objeto cambia, podría disparar un evento también. Simplemente no quiero sondear con una declaración if() innecesariamente.

import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStreamReader; 

/* 
* Example of checking a variable for changes. 
* Uses dumb if() and polls continuously. 
*/ 
public class NotAvoidingPolling { 

    public static void main(String[] args) { 

     boolean typedA = false; 
     String input = ""; 

     System.out.println("Type 'a' please."); 

     while (true) { 
      InputStreamReader isr = new InputStreamReader(System.in); 
      BufferedReader br = new BufferedReader(isr); 

      try { 
       input = br.readLine(); 
      } catch (IOException ioException) { 
       System.out.println("IO Error."); 
       System.exit(1); 
      } 

      // contrived state change logic 
      if (input.equals("a")) { 
       typedA = true; 
      } else { 
       typedA = false; 
      } 

      // problem: this is polling. 
      if (typedA) System.out.println("Typed 'a'."); 
     } 
    } 
} 

Al ejecutar este salidas:

Type 'a' please. 
a 
Typed 'a'. 

En algunos foros la gente sugirió el uso de un observador. Y aunque esto desacopla el controlador de eventos de la clase que se observa, todavía tengo un if() en un bucle para siempre.

import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStreamReader; 
import java.util.Observable; 
import java.util.Observer; 

/* 
* Example of checking a variable for changes. 
* This uses an observer to decouple the handler feedback 
* out of the main() but still is polling. 
*/ 
public class ObserverStillPolling { 

    boolean typedA = false; 

    public static void main(String[] args) { 

     // this 
     ObserverStillPolling o = new ObserverStillPolling(); 

     final MyEvent myEvent = new MyEvent(o); 
     final MyHandler myHandler = new MyHandler(); 
     myEvent.addObserver(myHandler); // subscribe 

     // watch for event forever 
     Thread thread = new Thread(myEvent); 
     thread.start(); 

     System.out.println("Type 'a' please."); 

     String input = ""; 

     while (true) { 
      InputStreamReader isr = new InputStreamReader(System.in); 
      BufferedReader br = new BufferedReader(isr); 

      try { 
       input = br.readLine(); 
      } catch (IOException ioException) { 
       System.out.println("IO Error."); 
       System.exit(1); 
      } 

      // contrived state change logic 
      // but it's decoupled now because there's no handler here. 
      if (input.equals("a")) { 
       o.typedA = true; 
      } 
     } 
    } 
} 

class MyEvent extends Observable implements Runnable { 
    // boolean typedA; 
    ObserverStillPolling o; 

    public MyEvent(ObserverStillPolling o) { 
     this.o = o; 
    } 

    public void run() { 

     // watch the main forever 
     while (true) { 

      // event fire 
      if (this.o.typedA) { 
       setChanged(); 

       // in reality, you'd pass something more useful 
       notifyObservers("You just typed 'a'."); 

       // reset 
       this.o.typedA = false; 
      } 

     } 
    } 
} 

class MyHandler implements Observer { 
    public void update(Observable obj, Object arg) { 

     // handle event 
     if (arg instanceof String) { 
      System.out.println("We received:" + (String) arg); 
     } 
    } 
} 

La ejecución de este salidas:

Type 'a' please. 
a 
We received:You just typed 'a'. 

Estaría bien si el if() fue un NOOP en la CPU. Pero en realidad está comparando cada pase. Veo la carga real de la CPU. Esto es tan malo como una encuesta. Tal vez pueda acelerarlo con un sueño o comparar el tiempo transcurrido desde la última actualización, pero esto no se basa en eventos. Es solo menos encuestas. Entonces, ¿cómo puedo hacer esto de manera más inteligente? ¿Cómo puedo ver un POJO para ver los cambios sin votación?

En C# parece que hay algo interesante llamado propiedades. No soy un chico C#, así que quizás esto no sea tan mágico como creo.

private void SendPropertyChanging(string property) 
{ 
    if (this.PropertyChanging != null) { 
    this.PropertyChanging(this, new PropertyChangingEventArgs(property)); 
    } 
} 

Respuesta

7

Sí, el manejo de eventos es la clave de su problema. El fragmento de C# que muestra también usa el manejo de eventos para indicar a los oyentes que se ha cambiado una propiedad. Java también tiene estos PropertyChangeEvent, que se usa para la mayoría de los beans. (Por ejemplo: los componentes Swing los usan).

Puede, sin embargo, hacer esto manualmente: (aunque esto no es tan POJO más, sino más bien un Java Bean)

EventBean.java:

import java.util.LinkedList; 
import java.util.List; 

public class EventBean { 

    private final List<Listener> listeners = new LinkedList<Listener>(); 

    protected final <T> void firePropertyChanged(final String property, 
      final T oldValue, final T newValue) { 
     assert(property != null); 
     if((oldValue != null && oldValue.equals(newValue)) 
       || (oldValue == null && newValue == null)) 
      return; 
     for(final Listener listener : this.listeners) { 
      try { 
       if(listener.getProperties().contains(property)) 
        listener.propertyChanged(property, oldValue, newValue); 
      } catch(Exception ex) { 
       // log these, to help debugging 
       ex.printStackTrace(); 
      } 
     } 
    } 

    public final boolean addListener(final Listener x) { 
     if(x == null) return false; 
     return this.listeners.add(x); 
    } 
    public final boolean removeListener(final Listener x) { 
     return this.listeners.remove(x); 
    } 

} 

Listener.java:

import java.util.Collections; 
import java.util.Set; 
import java.util.TreeSet; 

// Must be in same package as EventBean! 
public abstract class Listener { 

    private final Set<String> properties; 

    public Listener(String... properties) { 
     Collections.addAll(this.properties = new TreeSet<String>(), properties); 
    } 

    protected final Set<String> getProperties() { 
     return this.properties; 
    } 

    public abstract <T> void propertyChanged(final String property, 
      final T oldValue, final T newValue); 
} 

e implementar su clase como esta:

public class MyBean extends EventBean { 

    private boolean typedA; 

    public void setTypedA(final boolean newValue) { 
     // you can do validation on the newValue here 
     final boolean oldValue = typedA; 
     super.firePropertyChanged("typedA", oldValue, newValue); 
     this.typedA = newValue; 
    } 
    public boolean getTypedA() { return this.typedA; } 

} 

que se puede utilizar como:

MyBean x = new MyBean(); 
x.addListener(new Listener("typedA") { 
    public <T> void propertyChanged(final String p, 
       final T oldValue, final T newValue) { 
     System.out.println(p + " changed: " + oldValue + " to " + newValue); 
     // TODO 
    } 
}); 

x.setTypedA(true); 
x.setTypedA(false); 
x.setTypedA(true); 
x.setTypedA(true); 
+0

Nota: Observable/Observer es como el manejo de eventos. Este es un patrón de controlador/observador de eventos híbrido, que es muy fácil de usar. Se puede mejorar aún más, de modo que el "firePropertyChanged" utiliza la reflexión para modificar el campo privado. Entonces todos los métodos setter son muy limpios y mantenibles, aunque el rendimiento puede disminuir. – Pindatjuh

7

No entiende bien el patrón del observador si su implementación utiliza sondeo. En el patrón de observador, el método que cambia el estado del sujeto hará que los observadores suscritos sean notificados antes de que se complete.

Obviamente, esto significa que el sujeto debe ser compatible con el patrón del observador, no puede usar ningún objeto antiguo de Java como sujeto en el patrón del observador.

Es posible que también desee consultar el wikipedia example for the observer pattern.

+0

El ejemplo wikipedia es lo que he modificado. Pero creo que me equivoqué al pensar que el lector de entrada de datos está sondeando. Es solo un truco inventado mientras (verdadero) por el bien de la interactividad. Si rompo la cuerda en una clase con un setter y disparo el evento una vez desde el setter, lo grok. Estaba estableciendo un punto de interrupción en el ciclo while pensando que estaba enviando actualizaciones a los observadores innecesariamente. Es un sondeo, pero solo porque está viendo el ingreso de texto. Gracias. – squarism

0

Quizás la solución es wait()/notify() o una de las utilidades de simultaneidad de Java. Estos constructos evitan el sondeo de "espera ocupada" que parece ser el objetivo de la pregunta.

Se utilizan con varios hilos. Un hilo entrega el evento, otros responden a él. Aquí hay un ejemplo:

class SyncQueue { 

    private final Object lock = new Object(); 

    private Object o; 

    /* One thread sends "event" ... */ 
    void send(Object o) 
    throws InterruptedException 
    { 
    if (o == null) 
     throw new IllegalArgumentException(); 
    synchronized (lock) { 
     while (this.o != null) 
     lock.wait(); 
     this.o = o; 
     lock.notifyAll(); 
    } 
    } 

    /* Another blocks (without burning CPU) until event is received. */ 
    Object recv() 
    throws InterruptedException 
    { 
    Object o; 
    synchronized (lock) { 
     while (this.o == null) 
     lock.wait(); 
     o = this.o; 
     this.o = null; 
     lock.notifyAll(); 
    } 
    return o; 
    } 

} 
1

De hecho, el hilo adicional no es necesario. Debe dejar ObserverStillPolling ampliar Observable y notificar a los observadores cuando input.equals("a").

Sí, esto significa que el POJO en sí debería extenderse a Observable y notificar a los observadores.


Dicho esto, el Observer/Observable están mal diseñados. Sugeriría que crees tu propio interface PropertyChangeListener y dejes que tu POJO lo implemente. Luego, permita que el POJO se registre con un observador durante la construcción o lo haga de forma dinámica consultando en algún lugar instanceof PropertyChangeListener si corresponde.

Cuestiones relacionadas