web-dev-qa-db-fra.com

EventBus / PubSub vs (extensions réactives) RX en ce qui concerne la clarté du code dans une application à thread unique

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 .

37
jhegedus

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.

1. Plus déclaratif, moins d'effets secondaires et moins d'état mutable.

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.

2. Moyens d'éliminer pépins et les calculs redondants. (ReactFX uniquement)

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.

3. Lien clair entre le producteur d'événements et le consommateur d'événements.

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.

4. Les événements publiés par un objet sont manifestés dans son API.

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.

30
Tomas Mikula

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 (...);

.

5
Roger Garzon Nieto

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
}

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.

1
Luciano

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 ?

    • Il décrit une machine d'état.
    • Elle est compositionnelle car la description se fait en utilisant des fonctions pures et des valeurs immuables.
  • EventBus n'est pas une manière compositionnelle de décrire une machine à états. Pourquoi pas ?

    • Vous ne pouvez pas prendre deux deux bus d'événements et les composer d'une manière qui donne un nouveau bus d'événements composé décrivant la machine d'état composée. Pourquoi pas ?
      • Les bus d'événements ne sont pas des citoyens de première classe (contrairement à l'événement/flux FRP).
        • Que se passe-t-il si vous essayez de faire des bus d'événement des citoyens de première classe?
          • Ensuite, vous obtenez quelque chose qui ressemble à FRP/RX.
      • Les états influencés par les bus d'événements ne sont pas pas
        • citoyens de première classe (c'est-à-dire des valeurs pures référentiellement transparentes contrairement au comportement/cellule de FRP)
        • lié aux bus d'événements de manière déclarative/fonctionnelle, à la place l'état est modifié impérativement déclenché par la gestion des événements

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.)

1
jhegedus