Actuellement, j'utilise une architecture/un modèle EventBus/ PubSub avec Scala (et JavaFX) pour implémenter une application simple d'organisation de notes (un peu comme un client Evernote avec certains fonctionnalité de cartographie mentale ajoutée) et je dois dire que j'aime vraiment EventBus par rapport au modèle d'observateur.
Voici quelques bibliothèques EventBus:
https://code.google.com/p/guava-libraries/wiki/EventBusExplained
http://eventbus.org (actuellement semble être en panne) c'est celui que j'utilise dans mon implémentation.
http://greenrobot.github.io/EventBus/
Voici une comparaison des bibliothèques EventBus: http://codeblock.engio.net/37/
EventBus est lié au modèle de publication-abonnement .
Cependant!
Récemment, j'ai suivi le Cours réactif de Coursera et j'ai commencé à me demander si l'utilisation de RXJava au lieu d'EventBus simplifierait encore plus le code de gestion des événements dans un application à thread unique ?
Je voudrais poser des questions sur les expériences des personnes qui ont programmé en utilisant les deux technologies (une sorte de bibliothèque Eventbus et une certaine forme de extensions réactives) (RX)): était-il plus facile de gérer la complexité de la gestion des événements en utilisant RX qu'avec une architecture de bus d'événements étant donné qu'il n'était pas nécessaire d'utiliser plusieurs threads ?
Je pose cette question parce que j'ai entendu dans les Reactive Lectures on Coursera que RX conduit à un code beaucoup plus propre qu'en utilisant le modèle d'observateur (c'est-à-dire qu'il n'y a pas de "callback hell"), mais je n'ai trouvé aucune comparaison entre l'architecture EventBus et RXJava . Il est donc clair que EventBus et RXJava sont meilleurs que le modèle d'observateur mais qui est meilleur dans une application à thread unique en termes de clarté et de maintenabilité du code?
Si je comprends bien, le principal argument de vente de RXJava est qu'il peut être utilisé pour produire des applications réactives s'il y a des opérations de blocage (par exemple, en attente d'une réponse d'un serveur).
Mais je ne me soucie pas du tout de l'asychronicité, tout ce qui m'importe, c'est de garder le code propre, démêlé et facile à raisonner dans une application à thread unique .
Dans ce cas, est-il toujours préférable d'utiliser RXJava que EventBus?
Je pense que EventBus serait une solution plus simple et plus propre et je ne vois aucune raison pour laquelle je devrais utiliser RXJava pour une application à thread unique en faveur d'un architecture simple EventBus.
Mais je peux me tromper!
Veuillez me corriger si je me trompe et expliquer pourquoi RXJava serait mieux qu'un simple EventBus dans le cas d'une application à thread unique où aucune opération de blocage n'est effectuée .
Voici ce que je considère comme les avantages de l'utilisation de flux d'événements réactifs dans une application synchrone à thread unique.
Les flux d'événements sont capables d'encapsuler la logique et l'état, laissant potentiellement votre code sans effets secondaires et variables mutables.
Prenons une application qui compte les clics sur les boutons et affiche le nombre de clics sous forme d'étiquette.
Solution JavaFX simple:
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!!!
});
Solution 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());
Aucune variable mutable n'est utilisée et l'affectation secondaire à label.textProperty()
est cachée derrière une abstraction.
Dans sa thèse de maîtrise, Eugen Kiss a proposé l'intégration de ReactFX avec Scala. En utilisant son intégration, la solution pourrait ressembler à ceci:
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)
Il est équivalent au précédent, avec l'avantage supplémentaire d'éliminer l'inversion de contrôle.
Les défauts sont des incohérences temporaires dans un état observable. ReactFX a les moyens de suspendre la propagation des événements jusqu'à ce que toutes les mises à jour d'un objet aient été traitées, en évitant à la fois les problèmes et les mises à jour redondantes. En particulier, jetez un œil à flux d'événements suspendables , Indicateur , InhiBeans et mon article de blog sur InhiBeans . Ces techniques reposent sur le fait que la propagation des événements est synchrone, donc ne se traduisent pas en rxJava.
Le bus d'événements est un objet global auquel n'importe qui peut publier et s'abonner. Le couplage entre producteur d'événements et consommateur d'événements est indirect et donc moins clair. Avec les flux d'événements réactifs, le couplage entre producteur et consommateur est beaucoup plus explicite. Comparer:
Bus d'événements:
class A {
public void f() {
eventBus.post(evt);
}
}
// during initialization
eventBus.register(consumer);
A a = new A();
La relation entre a
et consumer
n'est pas claire en regardant uniquement le code d'initialisation.
Flux d'événements:
class A {
public EventStream<MyEvent> events() { /* ... */ }
}
// during initialization
A a = new A();
a.events().subscribe(consumer);
La relation entre a
et consumer
est très explicite.
En utilisant l'exemple de la section précédente, dans l'exemple de bus d'événements, l'API de A
ne vous indique pas quels événements sont publiés par les instances de A
. En revanche, dans l'exemple de flux d'événements, l'API de A
indique que les instances de A
publient des événements de type MyEvent
.
Je pense que vous devez utiliser le rxjava car il offre beaucoup plus de flexibilité. Si vous avez besoin d'un bus, vous pouvez utiliser une énumération comme celle-ci:
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 (...);
.
Selon mon commentaire ci-dessus, JavaFx a une classe ObservableValue qui correspond à RX Observable
(probablement ConnectableObservable
pour être plus précis, car il permet plus d'un abonnement ). J'utilise la classe implicite suivante pour convertir de RX en JFX, comme ceci:
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
}
}
}
Vous permet ensuite d'utiliser des liaisons de propriétés comme ceci (c'est ScalaFX, mais correspond à Property.bind
dans JavaFX):
new Label {
text <== rxObs
}
Où rxObs
pourrait être par exemple:
val rxObs : rx.Observable[String] = Observable.
interval(1 second).
map{_.toString}.
observeOn{rx.lang.scala.schedulers.ExecutorScheduler(JavaFXExecutorService)}
qui est simplement un compteur qui s'incrémente à chaque seconde. N'oubliez pas d'importer la classe implicite. Je ne peux pas imaginer que ça devienne plus propre que ça!
Ce qui précède est un peu compliqué, en raison de la nécessité d'utiliser un planificateur qui fonctionne bien avec JavaFx. Voir this question pour un lien vers un Gist sur la façon dont JavaFXExecutorService
est implémenté. Il y a demande d'amélioration pour scala RX pour en faire un argument implicite, donc à l'avenir vous n'aurez peut-être plus besoin du .observeOn
appel.
J'ai appris une ou deux choses depuis que j'ai posé cette question il y a 2 ans, voici ma compréhension actuelle (comme expliqué dans le FRP de Stephen livre ):
Les deux essaient d'aider à décrire une machine à états, c'est-à-dire à décrire comment l'état du programme change en réponse aux événements.
La principale différence entre EventBus et FRP est la compositionnalité :
Qu'est-ce que la composition?
FRP est une manière compositionnelle de décrire une machine d'état, et le bus d'événements ne l'est pas. Pourquoi ?
EventBus n'est pas une manière compositionnelle de décrire une machine à états. Pourquoi pas ?
En résumé, EventBus n'est pas compositionnel, car la signification et le comportement d'un EventBus composé (c'est-à-dire l'évolution temporelle de l'état qui est influencé par ledit EventBus composé) dépend du temps (c'est-à-dire l'état des parties du logiciel qui sont non inclus explicitement dans la déclaration du EventBus composé). En d'autres termes, si j'essayais de déclarer un EventBus composé, il ne serait pas possible de déterminer (simplement en regardant la déclaration de l'EventBus composé) quelles règles régissent l'évolution des états des États qui sont influencés par l'EventBus composé, cela contraste avec FRP, où cela peut être fait.)