Просмотр переменной для изменений без опроса
Я использую фреймворк под названием "Обработка", который в основном представляет собой апплет Java. У него есть возможность делать ключевые события, потому что Applet может. Вы также можете перевернуть свои собственные обратные вызовы в родительский. Я не делаю этого прямо сейчас и, возможно, это решение. Пока я ищу более POJO-решение. Поэтому я написал несколько примеров, чтобы проиллюстрировать мой вопрос.
Пожалуйста, проигнорируйте использование ключевых событий в командной строке (консоли). Конечно, это было бы очень чистое решение, но это невозможно в командной строке, и мое фактическое приложение не является приложением командной строки. Фактически, ключевое событие было бы хорошим решением для меня, но я пытаюсь понять события и опросы, выходящие за рамки конкретных проблем, связанных с клавиатурой.
Оба этих примера перевернули логическое значение. Когда логические перевороты, я хочу что-то выстрелить один раз. Я мог бы обернуть логическое значение в Object, поэтому, если объект изменится, я также могу запустить событие. Я просто не хочу опросить с помощью 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'.
На некоторых форумах люди предлагали использовать Observer. И хотя это отделяет обработчик событий от наблюдаемого класса, у меня все еще есть if() в цикле 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 decoupled now because there 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'.
Мне будет хорошо, если if() был NOOP на CPU. Но это действительно сравнивает каждый проход. Я вижу реальную загрузку процессора. Это так же плохо, как опрос. Я могу, возможно, загнать его обратно сон или сравнить прошедшее время с момента последнего обновления, но это не связано с событиями. Это всего лишь меньше опросов. Итак, как я могу сделать это умнее? Как я могу наблюдать POJO за изменения без опроса?
В С#, кажется, есть что-то интересное, называемое свойствами. Я не парень С#, поэтому, возможно, это не так волшебно, как я думаю.
private void SendPropertyChanging(string property)
{
if (this.PropertyChanging != null) {
this.PropertyChanging(this, new PropertyChangingEventArgs(property));
}
}
Ответы
Ответ 1
Да, обработка событий - это ключ к вашей проблеме. Выделенный фрагмент С# также использует обработку событий, чтобы сообщить слушателям, что свойство изменено. Java также имеет эти PropertyChangeEvent, который используется для большинства Beans. (Например: компоненты 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);
Ответ 2
Вы неправильно понимаете шаблон наблюдателя, если его реализация использует опрос. В шаблоне наблюдателя метод, который изменяет состояние объекта, заставит подписчиков наблюдателей быть уведомленными до их завершения.
Очевидно, это означает, что субъект должен поддерживать шаблон наблюдателя, вы не можете использовать какой-либо простой старый объект Java как объект в шаблоне наблюдателя.
Вы также можете посмотреть пример wikipedia для шаблона наблюдателя.
Ответ 3
На самом деле лишний поток не нужен. Вы должны позволить ObserverStillPolling
расширить Observable
и уведомить наблюдателей, когда input.equals("a")
.
Да, это означает, что сам POJO должен расширять Observable
и уведомлять самих наблюдателей.
Тем не менее, Observer/Observable
на самом деле плохо разработаны. Я бы посоветовал сделать свой собственный interface PropertyChangeListener
и позволить вашему POJO реализовать его. Затем пусть либо POJO регистрируется со наблюдателем во время построения, либо динамически, проверив где-нибудь instanceof PropertyChangeListener
, если это применимо.
Ответ 4
Возможно, решение есть wait()
/notify()
или одна из утилит Java concurrency. Эти конструкции избегают опроса "ожидание-ожидание", который, кажется, является точкой вопроса.
Они используются с несколькими потоками. Один поток передает событие, другие отвечают на него. Вот пример:
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;
}
}