現在、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よりも優れている理由を説明してください。 。
以下は、単一スレッド同期アプリケーションでリアクティブイベントストリームを使用する利点として私が見ているものです。
イベントストリームはロジックと状態をカプセル化することができ、副作用や可変変数なしでコードを残すことができます。
ボタンのクリックをカウントし、クリック数をラベルとして表示するアプリケーションを考えてみましょう。
プレーンな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)
_
これは以前のコントロールと同等で、制御の反転を排除するという追加の利点があります。
グリッチは、観測可能な状態での一時的な不整合です。 ReactFXには、オブジェクトのすべての更新が処理されるまでイベントの伝播を一時停止する手段があり、グリッチと冗長な更新の両方を回避します。特に、 一時停止可能なイベントストリーム 、 インジケーター 、 InhiBeans および InhiBeansに関する私のブログ投稿 をご覧ください。これらの手法は、イベントの伝播が同期しているため、rxJavaに変換されないという事実に依存しています。
イベントバスは、誰でもパブリッシュおよびサブスクライブできるグローバルオブジェクトです。イベントプロデューサーとイベントコンシューマーの間の結合は間接的であるため、不明確です。リアクティブイベントストリームを使用すると、プロデューサーとコンシューマー間の結合がより明確になります。比較:
イベントバス:
_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
の関係は非常に明確です。
前のセクションの例を使用すると、イベントバスのサンプルでは、A
のAPIはA
のインスタンスによって発行されるイベントを通知しません。一方、イベントストリームのサンプルでは、A
のAPIはA
のインスタンスがタイプMyEvent
のイベントをパブリッシュすると述べています。
柔軟性がはるかに高いため、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 (...);
。
上記の私のコメントのとおり、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
呼び出しが不要になる可能性があります。
2年前にこの質問をしてから1つまたは2つのことを学びました。これが私の現在の理解です(StephenのFRP book で説明されています):
どちらも状態マシンを説明するのに役立ちます。つまり、イベントに応答してプログラムの状態がどのように変化するかを説明します。
EventBusとFRPの主な違いはcompositionalityです。
構成とは何ですか?
FRPはステートマシンを記述する構成的な方法であり、イベントバスはそうではありません。どうして ?
EventBusは、ステートマシンを記述するための構成的な方法ではありません。何故なの ?
要約すると、EventBusは構成的ではありません。構成されたEventBusの意味と動作(つまり、構成されたEventBusの影響を受ける状態の時間発展)は、時間(つまり、ソフトウェアのこれらの部分の状態含まれませんexplicitly構成されたEventBusの宣言)。言い換えると、合成されたEventBusを宣言しようとすると、(合成されたEventBusの宣言を見ただけでは)合成されたEventBusの影響を受ける状態の状態の進化を支配する規則を決定できません。これは、FRPとは対照的です。