反应式编程与事件驱动编程有何不同?

Posted

技术标签:

【中文标题】反应式编程与事件驱动编程有何不同?【英文标题】:How is reactive programming different than event-driven programming? 【发布时间】:2016-04-02 09:31:45 【问题描述】:

我正在学习 javascript 中的响应式编程和函数式响应式编程。我很困惑。

Wikipedia 表示,编写响应式代码的方法有很多种,例如命令式、OORP 和函数式。我想知道事件驱动是否只是编写响应式代码的另一种方式?

响应式编程与 Promises 有什么关系?我认为 promises 是事件驱动和回调地狱的替代方案。

【问题讨论】:

相关:Advantage of Functional Reactive Programming over event-listeners 签出:oreilly.com/ideas/reactive-programming-vs-reactive-systems 阅读这篇文章,其中讨论了所有有用的代码示例mydaytodo.com/pros-cons-of-reactive-programming,其中有一些代码示例可供启动。 【参考方案1】:

反应式编程与事件驱动编程有何不同?

事件驱动编程围绕所谓的事件展开,事件是在发生某些事情时程序“触发”的抽象事物。代码中的其他地方“监听”事件,并在事件发生时响应他们需要做的事情。例如,事件可能是“用户按下了此按钮”或“打印机已完成打印您的文档”。

反应式编程处理数据。归根结底,这是事件驱动编程的一个特例。事件:数据发生了变化。事件处理程序:更改更多数据(如果适用)。当您想到电子表格时,通常会澄清这个概念。如果您设置cell1 = cell2 + cell3,这将隐式设置cell2cell3 的数据更改事件上的两个事件处理程序以更新cell1 的数据。 cell1 的数据没有这样的事件处理程序,因为没有单元格依赖于它的值。


TL;DR;

Wikipedia 表示,编写响应式代码的方法有很多种,例如命令式、OORP 和函数式。我想知道事件驱动是否只是编写响应式代码的另一种方式?

事件驱动编程的思想与命令式、OO 和函数式的思想是正交的。

Imperitive programming:专注于改变你的程序的状态将实现你想要的。大多数计算机都是命令式的(而不是declarative programming),而高级语言有时是声明性的。相比之下,声明式编程处理的是编写代码来指定您希望它做什么,而不是您希望代码如何做。 Object Oriented programming:处理所谓的对象,或带有相关方法的数据包。与函数式编程不同,因为方法能够访问与对象关联的数据。 Functional programming:处理可重用的函数,或接受输入和输出的过程。这与 OO 编程不同,因为函数传统上无法将数据与输入和输出以外的函数关联起来。

Event driven programming:构建你的程序以处理(“处理”)程序中发生的其他事情(“事件”)。换句话说,它在逻辑上像这样构造你的代码

When Event1 happens
    do A and B

When Event2 happens
    do B and C

但是有很多方法可以编写这段代码,实际上有很多方法可以命令式地编写代码,还有很多方法可以函数式地编写,等等。不过,这里有一些例子。

命令式(使用事件循环):

while(true)
    // some other code that you need to do...

    if Event1 then
        do A
        do B
    if Event2 then
        do B
        do C

面向对象(带后台线程):

// event queue
events = new EventQueue()

handler = new EventHandler()
// creates background thread
Thread.DoInBackground(handler.listenForEvents(events))

// ... other code ...

// fire an event!
events.enqueue(new Event1())

// other file
class EventHandler
    Func listenForEvents(events)
        while(true)
            while events.count > 0
                newEvent = event.dequeue()
                this.handleEvent(newEvent)
            Thread.Sleep(Time.Seconds(1))

    Func handleEvent(event)
        if event is Event1
            this.A()
            this.B()
        if event is Event2
            this.B()
            this.C()

    Func A()
        // do stuff
        return

    Func B()
        // do stuff
        return

    Func C()
        // do stuff
        return

功能性(事件的语言支持)

on Event(1) do Event1Handler()
on Event(2) do Event2Handler()

Func Event1Handler()
    do A()
    do B()

Func Event2Handler()
    do B()
    do C()

Func A()
    // do stuff
    return

Func B()
    // do stuff
    return

Func C()
    // do stuff
    return

// ... some other code ...

// fire! ... some languages support features like this, and others have
// libraries with APIs that look a lot like this.
fire Event(1)

反应式编程与 Promises 有什么关系?

Promises 是对程序执行流程的抽象,可以总结如下:

提问者:当你做完你正在做的事情时,你会给我回电话吗? 回答者:没问题,我保证

这里并没有什么特别之处,只是它是另一种考虑代码执行顺序的方式。例如,当您调用远程机器时,promise 很有用。有了 Promise,你可以说“当你从这个远程调用返回时给我回电!”。无论您使用哪个库,然后承诺当它从远程机器上取回某些东西时都会给您回电。通常,这很有用,因为它可以让您在此期间执行其他操作,而无需等待调用返回。

妙语:有很多不同风格的代码,但它们在事件驱动和反应式编程模式中的作用并不大。据我所知,您可以使用大多数语言进行事件驱动和/或反应式编程。

【讨论】:

Promise 严格来说不仅仅是一个执行流程:它们是一个持久性模型,代表该执行的状态及其最终输出。因为它们是持久的,所以它们可以被存储、共享、引用、传递。然而,在纯粹的事件驱动系统中,如果您事后开始收听,您将缺乏历史记录,并且您(通常)必须收听所有事件,才能听到任何事件。 Promise 使您能够封装和订阅有限的、单一用途的事件流,并且还可以在将来的任何时间检查该事件流的状态。 总的来说,这篇文章是一本很好的原则读物。有关更多信息,请参阅:oreilly.com/ideas/reactive-programming-vs-reactive-systems 我喜欢你的回答比公认的要好,但所有这一切似乎都归结为我们行业看似永远存在的产生新流行语的需求。对我来说,您关于区分事件驱动编程和所谓的“反应式编程”的说法有些牵强。 “反应式编程处理数据。归根结底,这是事件驱动编程的一个特例。”我想,对于那些对事件是什么看法有限的人来说,这只是一个特例。无论哪种方式,贫血的流行语都会死!!! @JasonBunting 当然,流行语本身并没有用,但是区分概念的更一般和特定版本是相对正常的。我们说“物体”、“机器”、“汽车”、“汽车”、“轿车”、“本田思域”都是前者的特例。当有人说“事件驱动编程”时,如果您熟悉这两个术语,这应该会在您的脑海中产生与“反应式编程”不同的概念图像。 @FrankBryce - 我承认这一点,我只是在抱怨新流行语的倾向。虽然“本田思域”可能具有其他“轿车”实例可能没有的独特功能,但与“事件驱动编程, “除了一个简洁的新流行词;如果你问我,这几乎不是一个很好的功能。这样的流行语只会使原本可能清晰的对话变得狭窄。我想在与那些不了解既定白话的人交谈时,这是可以预料的。【参考方案2】:

响应式编程与 Promises 有什么关系?我认为 Promise 是事件驱动和回调地狱的替代方案。

在实践中,这两者是相关的,我喜欢将 Promises 称为通向函数式反应式编程的网关药物。

+----------------------+--------+-------------+
|                      |  Sync  |    Async    |
+----------------------+--------+-------------+
| Single value or null | Option | Promise     |
| Multiple values      | List   | EventStream |
+----------------------+--------+-------------+

Promises 可以被认为是具有一个项目的 EventStreams,或者您可以将 EventStreams 视为随着时间推移的多个 Promise。

Promise 可以被链接,这已经接近于响应式编程:

getUser() // return promise
   .then((userId) => 
       return fetch("/users/"+userId)
   )
   .then((user) => 
       alert("Fetched user: " + user.name)
   )

bacon.js 也一样:

const userStream = userIdStream // EventStream of userIds
   .flatMapLatest((userId) => 
       return Bacon.fromPromise(fetch("/users/"+userId))
   )
const userNameStream = userStream.map((user) => user.name)
userNameStream.onValue((user) => 
   alert("Fetched user: " + user.name)
)

两个代码 sn-ps 都做同样的事情,但思维方式有很大不同:使用 promises,您正在考虑以清晰的方式处理具有异步步骤的单个操作 - 思维是必要的,您正在做事一步一步一步。使用 FRP,您可以说“通过应用这两个转换步骤从userIds 流创建用户名流”。当你有一个用户名流时,不关心它们来自哪里,并说“只要有新用户名,就将其显示给用户”。

FRP 编码风格将指导您将问题建模为值流(即随时间变化的值)以及这些值之间的关系。如果您已经了解 Promises,最初的学习曲线会更容易一些,但只有当您开始以不同的方式思考和建模问题时才能获得主要好处 - 使用 FRP 库进行命令式编程是可能的(如果不是很有用)。

【讨论】:

【参考方案3】:

差异主要与如何您“配置”(或声明)事物约定有关:当其他事情发生时,某事会发生什么。

在反应式编程中,您声明对更改的反应。您不必预先预见该更改所需的这种反应,您可以在以后随时添加 - declare - 这种反应。因此,它可能被视为“拉动”或“观察”策略。

因此,在反应式编程中,您连接到/观看您知道存在的数据。数据在这里至关重要。

示例:用户点击页面上的项目 -> 更新计数器用户点击了多少次。

示例计算器应用程序:计算器显示屏绑定到所有按钮,并根据显示屏上的任何更改(点击按钮)做出反应。按钮不知道他们的点击可以被任何其他部分利用。

在事件驱动编程中,您在命令式编写的代码中触发特定情况中的事件。您需要在这里明确说明,因为需要先触发事件才能稍后接收 - 因为基本上您 push 在代码的“更改发生”部分中的事件。所以这是一种“推动”策略。

因此,在事件驱动编程中,您推动某种情况可能会发生的事件被代码的其他部分接收。情况在这里很重要,数据无关紧要。

示例:有人访问了联系页面 -> 触发了一个事件(最终可能不会被任何监听器接收到,这是许多模块和库的典型情况)。

计算器应用示例:计算器显示只是一个监听器,按钮触发事件。按钮需要知道它们存在于特定上下文中(但是 - 由于事件侦听器模式 - 不必知道该上下文到底是什么),因此它们需要触发事件。

所以在大多数情况下,它们只是不同的约定。 看看这个简单的例子。命令式方法示例:

event: perform some operation on a, e.g. a += value, and trigger the event
listener: counter++

以及反应式声明方法示例:

counter: whenever an operation on a occurs, react with this: counter++

在最后一个示例中无需触发任何事情 - 您只需对可能发生的任何事情“挂钩”反应

那么,您可能会说,在响应式方法中,该反应绑定到 a,而在命令式事件驱动方法中,您推送一个事件,该事件稍后可以被侦听器接收 - 因为这种方法不相关无论如何,您可以将其更改为:a += value 以后再更改为其他任何内容,甚至完全删除 a。事件驱动的方法本质上与数据无关。

如您所见,响应式编程是面向数据的(数据的更改会与触发其他代码发生反应),而事件驱动的编程是面向流程的(无论数据是否发生变化以及发生什么变化(如果有的话)都无关紧要 - 你只需触发将由代码的其他部分接收的事件)。在后一种情况下,您需要知道这需要“通知”代码的其他部分,然后您必须预见应该触发事件。在前一种情况下,您不必这样做,您可以随时这样做,或者根本不这样做——不需要触发事件——但这里的诀窍是必须有你可以与之挂钩的“东西”你的反应声明,一种观察者,可以让你对观察到的变化做出反应。

【讨论】:

【参考方案4】:

响应式编程是关于流的,它可以是事件流,也可以是其他任何东西。它是在发射/宣布这些流或订阅/观看这些流或导致某些事件的流转换。所以这两种编程范式都是相关的。

【讨论】:

【参考方案5】:

对我来说,这就像将橙子与苹果进行比较。让我们尝试以一种简单的方式定义什么是什么,从而区分事物:

反应式编程是一种编程范式,当人们想要在 KnockoutJS 等库中实现类似于数据绑定的功能时,就会应用这种编程范式。 Excel 公式也是一个例子:所有单元格就像内存中的变量。有些只是保存一些数据,有些是从这些数据中计算出来的。如果前者改变,后者也会改变。注意范式是关于较低级别的实现;当有人谈论反应式编程时,他们指的是数据、它的变化以及它发生变异时会发生什么。

另一方面,事件驱动编程是关于系统架构的。根据该范式,事件和事件处理程序是系统的基础,一切都建立在它们之上和周围。常见的例子是 UI 和 Web 服务器多路复用。你觉得这一切有什么不同吗?范式应用于整个系统或子系统的层面。

响应式编程与 Promises 有什么关系?我认为承诺是 事件驱动和回调地狱的替代方案。

Promise 是一个实现并发和特定执行顺序的工具。它可以用于任何范例。

在实践中,范式服务于不同的目的和不同的层次。您可以使用一些响应式代码进行事件驱动设计。您可以拥有使用反应式设计模式的分布式系统。然而,事件最终是更高层次的概念。响应式是关于数据及其重新评估、实现方法或其细节,事件是从案例中自然产生并推动您的设计的东西。

【讨论】:

“事件驱动编程是关于系统架构的”——谁说的?你正试图把苹果变成橘子。每一个变化都可以被认为是一个事件。 UI 更改仍然是数据更改。 “Web 服务器多路复用”(无论如何),仍然 包含数据更改。这个世界上的每一个事件都是一个数据变化,反之亦然,如果你选择这样看的话。这就是成为一名开发人员的关键:您对问题领域的高保真视角在很大程度上决定了哪种方法最有意义。

以上是关于反应式编程与事件驱动编程有何不同?的主要内容,如果未能解决你的问题,请参考以下文章

反应式编程和消息队列的区别

反应式编程 - Node.js 中的 RxJS 与 EventEmitter

使用 Akka 的 Actor 模型和领域驱动设计构建反应式系统

iOS Swift:如何为 Swift 3 进行异步/反应式/事件编程

反应式编程 RxJava 设计原理解析

python IOmodel