2010-05-12 138 views
11

我正在使用一个名为Processing的框架,它基本上是一个Java小程序。它有能力做关键事件,因为Applet可以。您也可以将自己的回调放入父级。我现在没有这样做,也许这就是解决方案。现在,我正在寻找更多POJO解决方案。所以我写了一些例子来说明我的问题。在没有轮询的情况下观察变量的变化

请忽略在命令行(控制台)上使用键盘事件。当然,这将是一个非常干净的解决方案,但它不可能在命令行上,我的实际应用程序不是命令行应用程序。事实上,一个关键事件对我来说是一个很好的解决方案,但我试图理解事件和轮询,而不仅仅是键盘特定的问题。

这两个例子翻转布尔值。布尔翻转时,我想发射一次。我可以将布尔值封装在一个对象中,所以如果对象发生变化,我也可以触发一个事件。我只是不想用if()语句进行不必要的轮询。

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

运行此输出:

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

在一些论坛上的人使用一个观察员建议。尽管这样可以将事件处理程序从正在被观察的类中分离出来,但我仍然有一个if()永久循环。

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

运行此输出:

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

我很确定,如果如果()是一个在CPU上NOOP。但它真的比较每一个传球。我看到真正的CPU负载。这和投票一样糟糕。我可以通过睡眠来调节它,或者比较自上次更新以来的经过时间,但这不是事件驱动的。这只是较少的投票。那么我怎么能做到这一点更聪明?如何在没有投票的情况下观看POJO的更改?

在C#中似乎有一些有趣的属性。我不是C#的人,所以也许这不像我想象的那样神奇。

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

回答

7

是的,事件处理是你的问题的关键。您展示的C#代码片段还使用事件处理来告知侦听器属性已更改。 Java也有这些PropertyChangeEvent,用于大多数Bean。 (例如:Swing组件使用它们)。

你可以,但是,手动执行此操作:(虽然这并不像POJO了,但更多的一个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); 
} 

和实现类是这样的:

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

} 

您可以使用如:

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

注意:Observable/Observer很像事件处理。这是一个混合事件处理程序/观察者模式,它非常易于使用。它可以进一步改进,使“firePropertyChanged”使用反射来修改私人领域。然后所有的setter方法都非常干净和可维护,但性能可能会下降。 – Pindatjuh 2010-05-12 22:28:35

7

如果您的实施使用轮询,您会误解观察者模式。在观察者模式中,更改主体状态的方法将导致订阅观察者在完成之前得到通知。

显然,这意味着主体必须支持观察者模式,不能在观察者模式中使用任何普通的旧Java对象作为主体。

你也不妨看看wikipedia example for the observer pattern

+0

维基百科的例子是我修改。但我认为我错误地认为输入缓冲读取器正在轮询。这只是为了交互而设计的(真)循环。如果我用setter将字符串分解成一个类并从setter中发起一次事件,那么我会对它进行处理。我在while循环中设置了一个断点,认为它不必要地向观察者发送更新。这是轮询,但仅仅是因为它正在监视文本输入。 谢谢。 – squarism 2010-05-13 19:00:37

0

或许解决方案是wait()/notify()或Java的一个并发实用程序。这些构造避免了似乎成为问题点的“忙碌等待”轮询。

这些与多个线程一起使用。一个线程传递事件,其他线程对其进行响应。这里是一个例子:

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

事实上,额外的线程是不必要的。你应该让ObserverStillPolling延伸Observable并通知观察员时input.equals("a")

是的,这意味着POJO本身应该扩展Observable并通知观察者本身。


也就是说,Observer/Observable实际上设计得不好。我建议你自己创建interface PropertyChangeListener,让你的POJO实现它。然后,让POJO在施工期间向观察员注册本身,或者通过检查instanceof PropertyChangeListener(如果适用)来动态执行此操作。