2010-05-12 7 views
11

Ich verwende ein Framework namens Processing, die im Grunde ein Java-Applet ist. Es hat die Fähigkeit, Schlüsselereignisse zu tun, weil Applet es kann. Sie können auch Ihre eigenen Rückrufe von Arten in den übergeordneten Ordner rollen. Ich mache das jetzt nicht und vielleicht ist das die Lösung. Für jetzt suche ich nach einer mehr POJO Lösung. Also habe ich einige Beispiele geschrieben, um meine Frage zu illustrieren.Beobachten einer Variablen für Änderungen ohne Abfrage

Bitte ignorieren Sie Schlüsselereignisse in der Befehlszeile (Konsole). Sicherlich wäre das eine sehr saubere Lösung, aber es ist nicht in der Befehlszeile möglich und meine eigentliche App ist keine Befehlszeilen-App. In der Tat wäre ein Schlüsselereignis eine gute Lösung für mich, aber ich versuche Ereignisse und Abfragen zu verstehen, die über die Tastatur hinausgehen.

Beide Beispiele spiegeln einen booleschen Wert. Wenn der boolesche Flipflop ausgelöst wird, möchte ich einmal etwas auslösen. Ich könnte den Booleschen Wert in ein Objekt einfügen. Wenn sich das Objekt ändert, könnte ich also auch ein Ereignis auslösen. Ich will einfach nicht unnötig mit einer if() -Anweisung abfragen.

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

Ausführen dieser Ausgänge:

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

In einigen Foren Menschen mit einem Beobachter vorgeschlagen. Und obwohl dies den Ereignishandler von der beobachteten Klasse entkoppelt, habe ich immer noch ein if() für eine forever-Schleife.

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

Ausführen dieser Ausgänge:

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

würde ich in Ordnung sein, wenn der if() wurde ein NOOP auf der CPU. Aber es vergleicht wirklich jeden Durchgang. Ich sehe echte CPU-Last. Das ist so schlecht wie das Polling. Ich kann es vielleicht mit einem Schlaf zurückdrosseln oder die vergangene Zeit seit dem letzten Update vergleichen, aber dies ist nicht ereignisgesteuert. Es ist nur weniger Umfragen. Wie kann ich das klüger machen? Wie kann ich ein POJO für Änderungen ohne Abfrage sehen?

In C# scheint es etwas Interessantes namens Eigenschaften zu geben. Ich bin kein C# Typ, also ist das vielleicht nicht so magisch wie ich denke.

Antwort

7

Ja, Event-Handling ist der Schlüssel zu Ihrem Problem. Der C# -Snippet, den Sie anzeigen, verwendet ebenfalls die Ereignisbehandlung, um Listenern mitzuteilen, dass eine Eigenschaft geändert wurde. Java hat auch diese PropertyChangeEvents, die für die meisten Beans verwendet werden. (Zum Beispiel: Swing-Komponenten verwenden sie).

Sie können jedoch tun dies manuell: (obwohl dies nicht mehr als POJO ist, sondern mehr eine 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); 
} 

und implementieren Ihre Klasse wie folgt:

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

} 

Welche können Sie verwenden, wie:

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

Hinweis: Observable/Observer ist vergleichbar mit der Ereignisbehandlung. Dies ist ein Hybrid-Event-Handler/Beobachter-Muster, das sehr einfach zu verwenden ist. Es kann weiter verbessert werden, so dass "firePropertyChanged" Reflektion verwendet, um das private Feld zu modifizieren. Dann sind alle Setter-Methoden sehr sauber und wartbar, obwohl die Leistung abnehmen könnte. – Pindatjuh

7

Sie missverstehen das Beobachtermuster, wenn Ihre Implementierung von Polling verwendet. Im Beobachtermuster bewirkt die Methode, die den Status des Subjekts ändert, dass abonnierte Beobachter benachrichtigt werden, bevor sie abgeschlossen werden.

Offensichtlich bedeutet dies, dass das Subjekt das Beobachtermuster unterstützen muss. Sie können kein einfaches altes Java-Objekt als Subjekt im Beobachtermuster verwenden. Sie können auch die wikipedia example for the observer pattern betrachten.

+0

Das wikipedia Beispiel ist das, was ich geändert. Aber ich denke, ich bin falsch gelaufen, weil ich dachte, dass der Input-gepufferte Reader Polling ist. Es ist nur eine erfundene while (wahre) Schleife wegen der Interaktivität. Wenn ich die Saite zu einer Klasse mit einem Setter ausbringe und das Ereignis einmal vom Setter feuere, dann mache ich es. Ich stellte einen Breakpoint in der while-Schleife ein und dachte, dass er den Beobachtern unnötigerweise Updates zukommen lassen würde. Es wird gepollt, aber nur, weil es nach Texteingabe sucht. Danke. – squarism

0

Vielleicht ist die Lösung wait()/notify() oder eines der Parallelitätsprogramme von Java. Diese Konstrukte vermeiden das "busy-wait" -Polling, das der entscheidende Punkt zu sein scheint.

Diese werden mit mehreren Threads verwendet. Ein Thread liefert das Ereignis, andere antworten darauf. Hier ein Beispiel:

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

In der Tat ist das zusätzliche Gewinde unnötig. Sie sollten ObserverStillPollingObservable verlängern und die Beobachter benachrichtigen, wenn input.equals("a").

Ja, bedeutet dies, dass die POJO selbst Observable erstrecken sollte und die Beobachter selbst benachrichtigen.


Das sagte, die Observer/Observable sind eigentlich schlecht entworfen. Ich würde vorschlagen, dass Sie Ihre eigenen interface PropertyChangeListener und lassen Sie Ihre POJO es umsetzen zu ende Sorten. Dann lassen Sie entweder das POJO registrieren sich mit einem Beobachter während des Baus oder tun es dynamisch von irgendwo für instanceof PropertyChangeListener ggf. zu prüfen.