EventBus/PubSub vs (реактивные расширения) RX в отношении четкости кода в однопоточном приложении
В настоящее время я использую архитектуру/паттерн EventBus/PubSub с Scala (и JavaFX) для реализации простого приложения для организации заметок (например, вроде как клиент Evernote с некоторыми дополнительными функциями отображения разума), и я должен сказать, что мне действительно нравится EventBus над шаблоном наблюдателя.
Вот некоторые библиотеки EventBus:
https://code.google.com/p/guava-libraries/wiki/EventBusExplained
http://eventbus.org (в настоящее время кажется, что он не работает), это тот, который я использую в своей реализации.
http://greenrobot.github.io/EventBus/
Вот сравнение библиотек EventBus: http://codeblock.engio.net/37/
EventBus связан с шаблоном публикации-подписки.
Однако!
Недавно я принял реактивный курс Coursera и начал задаваться вопросом, использует ли RXJava вместо EventBus упростит код обработки событий еще больше в приложении однопоточное?
Я хотел бы спросить об опыте людей, которые запрограммировали использование обеих технологий (какая-то библиотека eventbus и какая-то форма реактивные расширения (RX)): было ли проще справляться с сложностью обработки событий с использованием RX, чем с архитектурой шины событий , учитывая, что не нужно было использовать несколько потоков?
Я спрашиваю об этом, потому что я слышал в Reactive Lectures on Coursera, что RX приводит к значительно более чистому коду, чем к использованию шаблона наблюдателя (т.е. нет "обратного ада" ), однако я не нашел никакого сравнения между архитектурой EventBus и RXJava. Таким образом, ясно, что и EventBus, и RXJava лучше, чем шаблон наблюдателя, но , что лучше в однопоточных приложениях с точки зрения ясности кода и ремонтопригодности?
Если я правильно понимаю, что главная точка продажи RXJava заключается в том, что его можно использовать для создания чувствительных приложений, если есть операции блокировки ( например, ожидание ответа с сервера).
Но меня вообще не интересует асихотрофия, все, о чем я забочусь, это держать код в чистоте, распущенном и легко рассуждать в однопоточном приложении.
В этом случае, лучше ли использовать RXJava, чем EventBus?
Я думаю, что EventBus будет более простым и чистым решением, и я не вижу причин, по которым я должен использовать RXJava для однопоточного приложения в пользу простой архитектуры EventBus.
Но я могу ошибаться!
Пожалуйста, исправьте меня, если я ошибаюсь и объясню, почему RXJava будет лучше, чем простой EventBus в случае однопоточного приложения, где не выполняются операции блокировки.
Ответы
Ответ 1
Ниже приводится то, что я вижу в качестве преимущества использования потоков реактивных событий в однопоточном синхронном приложении.
1. Более декларативные, менее побочные эффекты и менее изменяемое состояние.
Потоки событий способны инкапсулировать логику и состояние, потенциально оставляя ваш код без побочных эффектов и изменяемых переменных.
Рассмотрим приложение, которое подсчитывает нажатия кнопок и отображает количество кликов в качестве метки.
Простое решение JavaFX:
private int counter = 0; // mutable field!!!
Button incBtn = new Button("Increment");
Label label = new Label("0");
incBtn.addEventHandler(ACTION, a -> {
label.setText(Integer.toString(++counter)); // side-effect!!!
});
Решение ReactFX:
Button incBtn = new Button("Increment");
Label label = new Label("0");
EventStreams.eventsOf(incBtn, ACTION)
.accumulate(0, (n, a) -> n + 1)
.map(Object::toString)
.feedTo(label.textProperty());
Никакая изменчивая переменная не используется, а побочное действие на label.textProperty()
скрывается за абстракцией.
В своей магистерской диссертации Eugen Kiss предложил интегрировать ReactFX с Scala. Используя его интеграцию, решение может выглядеть так:
val incBtn = new Button("Increment")
val label = new Label("0")
label.text |= EventStreams.eventsOf(incBtn, ACTION)
.accumulate(0, (n, a) => n + 1)
.map(n => n.toString)
Это эквивалентно предыдущему, с дополнительным преимуществом устранения инверсии управления.
2. Средства устранения сбоев и избыточных вычислений. (Только для ReactFX)
Глюки - это временные несоответствия в наблюдаемом состоянии. ReactFX имеет средства для приостановки распространения события до тех пор, пока все обновления для объекта не будут обработаны, избегая как сбоев, так и избыточных обновлений. В частности, посмотрите приостанавливаемые потоки событий, Indicator, InhiBeans и мое сообщение в блоге о InhiBeans. Эти методы основаны на том, что распространение событий синхронно, поэтому не переводите на rxJava.
3. Очистить соединение между производителем событий и потребителем событий.
Шина событий - это глобальный объект, который любой может публиковать и подписываться. Связь между производителем событий и потребителем событий является косвенной и поэтому менее ясной.
С реактивными потоками событий связь между производителем и потребителем гораздо более ясна. Для сравнения:
Шина событий:
class A {
public void f() {
eventBus.post(evt);
}
}
// during initialization
eventBus.register(consumer);
A a = new A();
Отношения между a
и consumer
не ясны, если посмотреть только на код инициализации.
Потоки событий:
class A {
public EventStream<MyEvent> events() { /* ... */ }
}
// during initialization
A a = new A();
a.events().subscribe(consumer);
Отношения между a
и consumer
очень явны.
4. События, опубликованные объектом, проявляются в его API.
Используя пример из предыдущего раздела, в примере шины данных a
API не сообщает вам, какие события публикуются экземплярами a
. С другой стороны, в образце потоков событий a
API заявляет, что экземпляры a
публикуют события типа MyEvent
.
Ответ 2
Я думаю, вам нужно использовать rxjava, потому что он обеспечивает гораздо большую гибкость.
Если вам нужен автобус, вы можете использовать перечисление следующим образом:
public enum Events {
public static PublishSubject <Object> myEvent = PublishSubject.create ();
}
//where you want to publish something
Events.myEvent.onNext(myObject);
//where you want to receive an event
Events.myEvent.subscribe (...);
.
Ответ 3
Согласно моему комментарию выше, JavaFx имеет класс ObservableValue, который соответствует RX Observable
(возможно, ConnectableObservable
точнее, поскольку он позволяет более одной подписки). Я использую следующий неявный класс для преобразования из RX в JFX, например:
import scala.collection.mutable.Map
import javafx.beans.InvalidationListener
import javafx.beans.value.ChangeListener
import javafx.beans.value.ObservableValue
import rx.lang.scala.Observable
import rx.lang.scala.Subscription
/**
* Wrapper to allow interoperability bewteen RX observables and JavaFX
* observables.
*/
object JfxRxImplicitConversion {
implicit class JfxRxObservable[T](theObs : Observable[T]) extends ObservableValue[T] { jfxRxObs =>
val invalListeners : Map[InvalidationListener,Subscription] = Map.empty
val changeListeners : Map[ChangeListener[_ >: T],Subscription] = Map.empty
var last : T = _
theObs.subscribe{last = _}
override def getValue() : T = last
override def addListener(arg0 : InvalidationListener) : Unit = {
invalListeners += arg0 -> theObs.subscribe { next : T => arg0.invalidated(jfxRxObs) }
}
override def removeListener(arg0 : InvalidationListener) : Unit = {
invalListeners(arg0).unsubscribe
invalListeners - arg0
}
override def addListener(arg0 : ChangeListener[_ >: T]) : Unit = {
changeListeners += arg0 -> theObs.subscribe { next : T => arg0.changed(jfxRxObs,last,next) }
}
override def removeListener(arg0 : ChangeListener[_ >: T]) : Unit = {
changeListeners(arg0).unsubscribe
changeListeners - arg0
}
}
}
Затем позволяет использовать привязки свойств (например, ScalaFX, но соответствует Property.bind
в JavaFX):
new Label {
text <== rxObs
}
Где rxObs
может быть, например:
val rxObs : rx.Observable[String] = Observable.
interval(1 second).
map{_.toString}.
observeOn{rx.lang.scala.schedulers.ExecutorScheduler(JavaFXExecutorService)}
который является просто счетчиком, который увеличивается каждую секунду. Просто не забудьте импортировать неявный класс. Я не могу себе представить, что это становится чище, чем это!
Вышеупомянутое немного свернуто из-за необходимости использовать планировщик, который хорошо играет с JavaFx. См. этот вопрос для ссылки на суть реализации JavaFXExecutorService
. Существует запрос улучшения для scala RX, чтобы сделать это неявным аргументом, поэтому в будущем вам может не понадобиться вызов .observeOn
.
Ответ 4
Я изучил одну или две вещи, так как я задал этот вопрос 2 года назад, вот мое текущее понимание (как объяснено в Stephen FRP book):
Оба пытаются помочь описать машину состояний, то есть описать, как изменяется состояние программы в ответ на события.
Ключевое различие между EventBus и FRP композиционностью:
-
Что такое композиция?
- Функциональное программирование является композиционным. Мы все можем согласиться на это. Мы можем взять любую чистую функцию, объединить ее с другими чистыми функциями и получить более сложную чистую функцию.
- Составность означает, что когда вы объявляете что-то, то на сайте объявления определяется все поведение объявленного объекта.
-
FRP - это композиционный способ описания конечного автомата, а event-bus - нет. Зачем?
- Он описывает конечный автомат.
- Это композиция, потому что описание выполняется с использованием чистых функций и неизменных значений.
-
EventBus не является композиционным способом описания конечного автомата. Почему бы и нет?
- Вы не можете взять любые два автобуса событий и составить их таким образом, чтобы создать новую сконфигурированную шину событий, описывающую скомпилированный конечный автомат. Почему нет?
- Банки событий не являются гражданами первого класса (в отличие от события/потока FRP).
- Что произойдет, если вы попытаетесь сделать автобусы для людей первого класса?
- Затем вы получаете что-то похожее на FRP/RX.
- Состояние, на которое влияют автобусы событий, не
- граждане первого класса (т.е. ссылочно прозрачные, чистые значения в отличие от поведения/ячейки FRP)
- привязаны к событиям в декларативном/функциональном режиме, вместо этого состояние изменено, принудительно вызванное обработкой событий
Таким образом, EventBus не является композиционным, потому что значение и поведение сконфигурированного EventBus (то есть эволюция времени состояния, на которое влияет упомянутый состав EventBus) зависят от времени (т.е. состояния тех частей программного обеспечения, которые не включены явно в объявлении созданного EventBus). Другими словами, если бы я попытался объявить составной EventBus, тогда было бы невозможно определить (просто взглянув на объявление сконфигурированного EventBus), какие правила определяют эволюцию состояния тех состояний, на которые влияет составленный EventBus, это в отличие от FRP, где это можно сделать.)