2010-05-12 13 views
11

J'utilise un framework appelé Processing qui est essentiellement une applet Java. Il a la capacité de faire des événements clés, car Applet peut. Vous pouvez également lancer vos propres rappels de sortes dans le parent. Je ne le fais pas en ce moment et peut-être que c'est la solution. Pour l'instant, je cherche une solution plus POJO. J'ai donc écrit quelques exemples pour illustrer ma question.Regarder une variable pour les changements sans polling

Veuillez ignorer l'utilisation des événements de clé sur la ligne de commande (console). Certes, ce serait une solution très propre, mais ce n'est pas possible sur la ligne de commande et mon application réelle n'est pas une application en ligne de commande. En fait, un événement clé serait une bonne solution pour moi, mais j'essaie de comprendre les événements et d'interroger au-delà des problèmes spécifiques au clavier.

Ces deux exemples retournent un booléen. Quand le booléen se retourne, je veux tirer quelque chose une fois. Je pourrais envelopper le booléen dans un objet, donc si l'objet change, je pourrais déclencher un événement aussi. Je ne veux pas interroger avec une instruction if() inutilement.

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'."); 
     } 
    } 
} 

L'exécution de cette sortie:

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

Sur certains forums les gens ont suggéré d'utiliser un observateur. Et bien que cela décompose le gestionnaire d'événements de la classe observée, j'ai toujours une boucle if() sur forever.

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); 
     } 
    } 
} 

L'exécution de cette sortie:

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

je serais ok si le cas() a été un NOOP sur la CPU. Mais c'est vraiment comparer chaque passe. Je vois la charge réelle du processeur. C'est aussi mauvais que le sondage. Je peux peut-être l'étrangler avec un sommeil ou comparer le temps écoulé depuis la dernière mise à jour, mais ce n'est pas un événement. C'est juste moins de sondage. Alors, comment puis-je faire plus intelligent? Comment puis-je regarder un POJO pour les changements sans interroger?

En C# il semble y avoir quelque chose d'intéressant appelé propriétés. Je ne suis pas un gars C# alors peut-être que ce n'est pas aussi magique que je pense.

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

Répondre

7

Oui, la gestion des événements est la clé de votre problème. L'extrait C# que vous affichez utilise également la gestion des événements pour indiquer aux écouteurs qu'une propriété est modifiée. Java a également ces PropertyChangeEvent, qui sont utilisés pour la plupart des Beans. (Par exemple: les composants Swing les utilisent).

Vous pouvez cependant faire manuellement: (bien que ce ne soit pas aussi POJO plus, mais plus d'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); 
} 

et mettre en œuvre votre classe comme ceci:

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 vous pouvez utiliser comme:

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

Remarque: Observable/Observer est assez similaire à la gestion des événements. Ceci est un modèle de gestionnaire d'événements/observateur hybride, qui est très facile à utiliser. Il peut être encore amélioré, de sorte que le "firePropertyChanged" utilise la réflexion pour modifier le champ privé. Alors toutes les méthodes de setter sont très propres et maintenables, bien que les performances puissent diminuer. – Pindatjuh

7

Vous avez mal compris le modèle d'observateur si votre implémentation utilise l'interrogation. Dans le modèle d'observateur, la méthode qui modifie l'état du sujet fera en sorte que les observateurs abonnés soient avertis avant la fin.

De toute évidence, cela signifie que le sujet doit prendre en charge le modèle d'observateur, vous ne pouvez pas utiliser un objet Java ancien comme sujet dans le modèle d'observateur. Vous pouvez également consulter le wikipedia example for the observer pattern.

+0

L'exemple wikipedia est ce que je modifié. Mais je pense que je me suis trompé en pensant que le lecteur inputbuffered est en train d'interroger. C'est juste une boucle artificielle (vraie) pour l'interactivité. Si je casse la corde à une classe avec un setter et déclenche l'événement une fois du setter, je le grok. Je mettais un point d'arrêt dans la boucle tout en pensant qu'il envoyait des mises à jour aux observateurs inutilement. Il interroge, mais seulement parce qu'il surveille la saisie de texte. Merci. – squarism

0

La solution est peut-être wait()/notify() ou l'un des utilitaires de concurrence de Java. Ces constructions évitent le sondage "occupé-attentiste" qui semble être le point de la question.

Ils sont utilisés avec plusieurs threads. Un thread livre l'événement, d'autres y répondent. Voici un exemple:

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

En fait, le thread supplémentaire n'est pas nécessaire. Vous devez laisser ObserverStillPolling étendre Observable et informer les observateurs lorsque input.equals("a").Oui, cela signifie que le POJO lui-même doit étendre Observable et informer les observateurs eux-mêmes.


Cela dit, les Observer/Observable sont en réalité mal conçus. Je suggérerais de créer votre propre interface PropertyChangeListener et de laisser votre POJO l'implémenter. Ensuite, soit le POJO s'enregistre auprès d'un observateur pendant la construction, soit il le fait dynamiquement en vérifiant quelque part instanceof PropertyChangeListener s'il y a lieu.