web-dev-qa-db-ja.com

シングルスレッドアプリケーションでのコードの明快さに関するEventBus / PubSub対(リアクティブ拡張)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は publish-subscribeパターン に関連しています。

ただし!

最近、私は Courseraによるリアクティブコース を受講し、EventBusの代わりに RXJava を使用すると、シングルスレッドアプリケーション?

両方の技術(ある種のイベントバスライブラリを使用してプログラミングした人の経験について質問したい 反応型拡張機能) (RX)):複数のスレッドを使用する必要がない場合、イベントバスアーキテクチャを使用するよりも、RXを使用してイベント処理の複雑さに取り組む方が簡単でした

Courseraの反応型講義 で聞いたことがあるので、これを求めています [〜#〜] rx [〜#〜] は、オブザーバーパターンを使用するよりもはるかにクリーンなコードになります(つまり、「コールバック地獄」はありません)しかし、EventBusアーキテクチャと RXJava の比較は見つかりませんでした。したがって、EventBusとRXJavaの両方がオブザーバーパターンよりも優れていることは明らかですが、シングルスレッドアプリケーションの方がコードの明快さと保守性の点で優れていますか?

私が正しく理解している場合、 RXJava の主なセールスポイントは、ブロッキング操作(サーバーからの応答の待機など)がある場合に応答アプリケーションを生成するために使用できることです。

しかし、私は非同期性についてはまったく気にしません。シングルスレッドアプリケーションでコードをきれいに保ち、もつれがなく、簡単に推論できることだけが重要です。 。

その場合、EventBusよりもRXJavaを使用する方が良いですか?

EventBusはよりシンプルでクリーンなソリューションになると思います。シングルスレッドアプリケーションにRXJavaを使用する必要がある理由はわかりません。シンプルなEventBusアーキテクチャ。

しかし、私は間違っているかもしれません!

シングルスレッドアプリケーションでブロッキング操作が実行されない場合に、RXJavaが単純なEventBusよりも優れている理由を説明してください。 。

37
jhegedus

以下は、単一スレッド同期アプリケーションでリアクティブイベントストリームを使用する利点として私が見ているものです。

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には、オブジェクトのすべての更新が処理されるまでイベントの伝播を一時停止する手段があり、グリッチと冗長な更新の両方を回避します。特に、 一時停止可能なイベントストリームインジケーターInhiBeans および InhiBeansに関する私のブログ投稿 をご覧ください。これらの手法は、イベントの伝播が同期しているため、rxJavaに変換されないという事実に依存しています。

3.イベントプロデューサーとイベントコンシューマー間の接続を明確にします。

イベントバスは、誰でもパブリッシュおよびサブスクライブできるグローバルオブジェクトです。イベントプロデューサーとイベントコンシューマーの間の結合は間接的であるため、不明確です。リアクティブイベントストリームを使用すると、プロデューサーとコンシューマー間の結合がより明確になります。比較:

イベントバス:

_class A {
    public void f() {
        eventBus.post(evt);
    }
}

// during initialization
eventBus.register(consumer);
A a = new A();
_

aconsumerの関係は、初期化コードだけを見ても明らかではありません。

イベントストリーム:

_class A {
    public EventStream<MyEvent> events() { /* ... */ }
}

// during initialization
A a = new A();
a.events().subscribe(consumer);
_

aconsumerの関係は非常に明確です。

4.オブジェクトによって発行されたイベントは、そのAPIで明示されます。

前のセクションの例を使用すると、イベントバスのサンプルでは、​​AのAPIはAのインスタンスによって発行されるイベントを通知しません。一方、イベントストリームのサンプルでは、​​AのAPIはAのインスタンスがタイプMyEventのイベントをパブリッシュすると述べています。

30
Tomas Mikula

柔軟性がはるかに高いため、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 (...);

5

上記の私のコメントのとおり、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ですが、JavaFXのProperty.bindに対応しています)。

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の実装方法の要点へのリンクについては、 this 質問を参照してください。これを暗黙の引数にするための 拡張要求 scala RXがあるため、将来的には.observeOn呼び出しが不要になる可能性があります。

1
Luciano

2年前にこの質問をしてから1つまたは2つのことを学びました。これが私の現在の理解です(StephenのFRP book で説明されています):

どちらも状態マシンを説明するのに役立ちます。つまり、イベントに応答してプログラムの状態がどのように変化するかを説明します。

EventBusとFRPの主な違いはcompositionalityです。

  • 構成とは何ですか?

  • FRPはステートマシンを記述する構成的な方法であり、イベントバスはそうではありません。どうして ?

    • ステートマシンを記述します。
    • 説明は純粋な関数と不変の値を使用して行われるため、構成的です。
  • EventBusは、ステートマシンを記述するための構成的な方法ではありません。何故なの ?

    • any2つのイベントバスを取得して、合成されたステートマシンを記述する新しい合成されたイベントバスを提供する方法でそれらを合成することはできません。何故なの ?
      • イベントバスは(FRPのイベント/ストリームとは対照的に)ファーストクラスの市民ではありません。
        • イベントバスをファーストクラスの市民にしようとするとどうなりますか?
          • 次に、FRP/RXに似たものを取得します。
      • イベントバスの影響を受ける状態はではありません
        • ファーストクラスの市民(FRPの動作/セルとは対照的に、参照的に透明で純粋な値)
        • 宣言的/機能的な方法でイベントバスに関連付けられている代わりに、イベント処理によって強制的にトリガーされて状態が変更される

要約すると、EventBusは構成的ではありません。構成されたEventBusの意味と動作(つまり、構成されたEventBusの影響を受ける状態の時間発展)は、時間(つまり、ソフトウェアのこれらの部分の状態含まれませんexplicitly構成されたEventBusの宣言)。言い換えると、合成されたEventBusを宣言しようとすると、(合成されたEventBusの宣言を見ただけでは)合成されたEventBusの影響を受ける状態の状態の進化を支配する規則を決定できません。これは、FRPとは対照的です。

1
jhegedus