关于事件处理:EventBus / PubSub vs(反应式扩展)RX,关于单线程应用程序中的代码清晰度

EventBus/PubSub vs (reactive extensions) RX with respect to code clarity in a single threaded application

目前,我使用一个带有scala(和javafx)的eventbus/pubsub架构/模式来实现一个简单的注释组织应用程序(有点像一个带有一些额外的思维映射功能的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与发布订阅模式相关。

然而!

最近,我参加了一个又一个的反应式课程,开始怀疑使用RXJava代替EventBus是否会在单线程应用程序中更简化事件处理代码?

我想问一下使用这两种技术(某种类型的EventBus库和某种形式的反应性扩展(RX))编程的人的经验:在不需要使用多线程的情况下,使用RX比使用事件总线体系结构更容易处理事件处理复杂性吗?

我之所以这样问,是因为我在课程的反应式讲座中听说Rx比使用观察者模式(即没有"回调地狱")能带来更干净的代码,但是我没有发现EventBus体系结构与RxJava之间的任何比较。很明显,eventbus和rxjava都比observer模式好,但是在单线程应用程序中,在代码清晰度和可维护性方面,哪一个更好?

如果我理解正确,RxJava的主要卖点是,如果存在阻塞操作(例如等待服务器的响应),它可以用来生成响应应用程序。

但我一点也不关心不对称性,我只关心在单线程应用程序中保持代码的干净、不被分类和易于推理。

在这种情况下,使用RxJava比使用EventBus更好吗?

我认为EventBus是一个更简单、更干净的解决方案,我不明白为什么我应该将RXJava用于单线程应用程序,而选择简单的EventBus架构。

但我可能错了!

如果我错了,请纠正我,并解释为什么在没有执行阻塞操作的单线程应用程序中,RxJava比简单的EventBus更好。


下面是我所看到的在单线程同步应用程序中使用反应性事件流的好处。

1。更多的声明性,更少的副作用和更少的可变状态。

事件流能够封装逻辑和状态,可能使代码没有副作用和可变变量。

考虑一个计算按钮单击次数并将单击次数显示为标签的应用程序。

纯javafx解决方案:

1
2
3
4
5
6
7
8
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解决方案:

1
2
3
4
5
6
7
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()的副作用赋值隐藏在抽象后面。

在他的硕士论文中,尤金·基斯提出了将ReactFx与scala结合起来。使用他的集成,解决方案可能如下所示:

1
2
3
4
5
6
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。

三。清除事件生成器和事件使用者之间的连接。

事件总线是任何人都可以发布和订阅的全局对象。事件生产者和事件消费者之间的耦合是间接的,因此不太清楚。对于反应性事件流,生产者和消费者之间的耦合更加明确。比较:

事件总线:

1
2
3
4
5
6
7
8
9
class A {
    public void f() {
        eventBus.post(evt);
    }
}

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

仅仅从初始化代码来看,aconsumer之间的关系并不清楚。

事件流:

1
2
3
4
5
6
7
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类型的事件。


我认为您必须使用RXJava,因为它提供了更多的灵活性。如果需要总线,可以使用如下枚举:

1
2
3
4
5
6
7
8
9
10
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 (...);

.


自从两年前我问这个问题以来,我学到了一两件事,以下是我目前的理解(如斯蒂芬的FRP书中所述):

两者都试图帮助描述状态机,即描述程序的状态如何随着事件的变化而变化。

EventBus和FRP之间的关键区别在于合成性:

  • 什么是作曲?

    • 函数式编程是复合的。在这一点上我们都能达成一致。我们可以取任意一个纯函数,与其他纯函数结合,得到的是一个更复杂的纯函数。
    • 组合性意味着当您声明某个东西时,然后在声明的站点上定义声明实体的所有行为。
  • FRP是描述状态机的一种组合方式,而事件总线则不是。为什么?

    • 它描述了一个状态机。
    • 它是合成的,因为描述是通过使用纯函数和不可变值完成的。
  • EventBus不是描述状态机的组合方式。为什么不?

    • 您不能采用任何两个事件总线并以给出描述组合状态机的新的组合事件总线的方式组合它们。为什么不?
      • 事件总线不是一级公民(与FRP的事件/流相反)。
        • 如果你想让一等公民参加活动会怎么样?
          • 然后你会得到类似于FRP/RX的东西。
      • 受事件总线影响的状态不是
        • 一等公民(即,相对于玻璃钢的行为/单元,参考透明、纯值)
        • 以声明性/功能性的方式绑定到事件总线,相反,状态是由事件处理强制触发的。

总之,eventbus不是复合的,因为复合eventbus的含义和行为(即受所述复合eventbus影响的状态的时间演化)取决于时间(即软件中未明确包含在复合eventbus声明中的那些部分的状态)。换句话说,如果我试图声明一个复合事件总线,那么就不可能确定(仅通过查看复合事件总线的声明)哪些规则控制受复合事件总线影响的那些状态的状态演化,这与FRP相反,在FRP中可以做到。)


根据我上面的评论,javafx有一个类可观察值,它与rx Observable相对应(可能ConnectableObservable更精确,因为它允许多个订阅)。我使用以下隐式类从rx转换为jfx,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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相对应):

1
2
3
new Label {
    text <== rxObs
}

其中,rxObs可以是:

1
2
3
4
val rxObs : rx.Observable[String] = Observable.
  interval(1 second).
  map{_.toString}.
  observeOn{rx.lang.scala.schedulers.ExecutorScheduler(JavaFXExecutorService)}

它只是一个每秒钟递增的计数器。只需记住导入隐式类。我无法想象它会变得更干净!

上面的内容有点复杂,因为需要使用一个能很好地使用JavaFX的调度程序。有关如何实现JavaFXExecutorService的要点,请参阅此问题。有一个对scala rx的增强请求,使其成为一个隐式参数,因此将来您可能不需要.observeOn调用。