Поскольку события запускаются и забываются (нет возвращаемых значений или недействительны), должно быть ясно, что асинхронные события или обработчики событий, которые возвращают значение, сами по себе являются противоречием или парадоксом.Когда асинхронный означает «ожидание без блокировки» и событие «уведомить без ожидания», вы, очевидно, создаете больше проблем, чем решений.Возвращаемое значение также подразумевает, что вызывающая сторона ожидает завершения операции и заинтересована в результате.
Я рекомендую реструктурировать ваше приложение.
Обработчик событий никогда не должен возвращать значение или быть асинхронным.
что если наблюдателю нужно больше времени, чтобы решить, потребляет ли он или нет?Как вы справляетесь с этим?
Это решение (или решения в целом) всегда зависит от состояния хотя бы одной переменной.Существуют две ситуации:
- , когда состояние известно в тот момент, когда наблюдатель получает уведомление, или
- состояние в данный момент неизвестно (наблюдателю требуется больше времени).
Ситуация 1) не требует ожидания, но ситуация 2) делает.
В случае ситуация 2) , изменениесостояние всегда вызывается операцией.Продолжительность выполнения этой операции определяет продолжительность времени ожидания.Эта операция должна вызвать событие, когда соответствующее состояние изменилось.
В общем, у вас есть три варианта ожидания:
- Продолжайте вращаться, пока не будет выполнено условие, подобное опросу (например, бесконечноеloop): while (true) {}.
- Используйте таймер и после истечения времени выполните действие
- Используйте события всякий раз, когда вам нужно ждать.
Первые два варианта заблокируют поток.Если поток совпадает с наблюдаемым потоком, то вы блокируете наблюдаемый и всех других ожидающих наблюдателей тоже.Если этот поток является потоком пользовательского интерфейса, то пользовательский интерфейс остановится и перестанет отвечать на запросы.События - это шаблон, который решит проблему блокировки.
Давайте представим следующий сценарий: вы хотите запустить определенную анимацию.У вас есть два ограничения: тип анимации зависит от того, какая клавиша была нажата И, прежде чем вы сможете запустить новую анимацию, вы должны дождаться завершения первой.Например, когда нажата TAB , переместите прямоугольник слева направо.Когда нажата ENTER , переместите прямоугольник сверху вниз.
Это вводит две ситуации ожидания: нажатие клавиши и завершение анимации.Чтобы обработать ожидание, вы должны создать и связать событие для каждой потенциальной ситуации ожидания: keyPressed
и animationStopped
событие:
Клавиша на клавиатуре нажала событие
Интерфейс, который будет реализован наблюдателем, который ожидает нажатия определенной клавиши:
interface IKeyPressedListener {
void onKeyPressed(int keyCode);
}
Интерфейс события, который должен быть реализован наблюдаемой, которая выставляет и вызывает событие:
interface IKeyPressedEvent {
void subscribeToKeyPressedEvent(IKeyPressedListener listener);
void unsubscribeToKeyPressedEvent(IKeyPressedListener listener);
}
Событие анимации
Интерфейс, который будет реализован наблюдателем, ожидающим остановки анимации:
interface IAnimationStoppedListener {
void onAnimationStopped();
}
Интерфейс события, который будет реализованнаблюдаемой, которая выставляет и вызывает событие:
interface IAnimationStoppedEvent {
void subscribeToAnimationStoppedEvent(IAnimationStoppedListener listener);
void unsubscribeToAnimationStoppedEvent(IAnimationStoppedListener listener);
}
Фактический слушатель события
Реализация класса, который воспроизводит анимацию при нажатой клавише:
class AnimationController implements IKeyPressedListener, IAnimationStoppedListener
{
// store the key that was pressed,
// so that an event that will be raised at a later can process it
private int keyCodeOfLastKeyPressed = 0;
// The reference to the class that exposes
// the keyPressedEvent by implementing IKeyPressedEvent
KeyboardController keyboardController;
// The reference to the class that exposes
// the animationStoppedEvent by implementing IAnimationStoppedEvent
AnimationPlayer animationPlayer;
// Constructor
public AnimationController() {
this.keyboardController = new KeyboardController();
this.animationPlayer = new AnimationPlayer();
// Subscribe to the key pressed event
this.keyboardController.subscribeToKeyPressedEvent(this);
}
@Override
public void onKeyPressed(int keyCode) {
if (this.animationPlayer.hasPlayingAnimation) {
// Instead of waiting that the animation completes
// subscribe to an event and store the relevant data
this.keyCodeOfLastKeyPressed = keyCode;
this.animationPlayer.subscribeToAnimationStoppedEvent(this);
}
else {
// There is no playing animation, so no need to wait
this.animationPlayer.playAnimation(keyCode);
}
}
// After a while this handler will be invoked by the event source.
@Override
public void onAnimationStopped() {
// To avoid memory leaks unsubscribe first
this.animationPlayer.unsubscribeToAnimationStoppedEvent(this);
// Since we stored the key code earlier, we can continue to process it
// and start a new animation that maps to a specific key
this.animationPlayer.playAnimation(this.keyCodeOfLastKeyPressed);
}
}
Следование шаблону наблюдателя позволяет избежать времени ожидания блокировки потока.Приложение может просто покинуть контекст и вернуться, когда произошло событие (в данном случае событие AnimationStopped
).Чтобы сохранить значение изменения (аргументы события) события, вводится частное общее поле, чтобы второй обработчик событий мог получить к нему доступ и, наконец, обработать его.