精华内容
下载资源
问答
  • 响应式编程 千次阅读
    2021-02-27 19:21:34

    响应式编程

    响应式编程是一种异步编程范式,它关注数据流和变化的传播。这意味着可以通过使用编程语言轻松地表示静态(例如数组)和动态(例如事件发射器)数据流。

    Reactive Streams为基于JVM的响应库提供了规范,它定义了一组接口和交互规则。在Java 9中,这些接口已经集成到java.util.concurrent.Flow类之下。

    在面向对象编程语言中,响应式编程通常以观察者模式的扩展呈现。还可以将响应式流模式和迭代器模式比较,一个主要的区别是,迭代器基于”拉“,而响应式流基于”推“。

    使用迭代器是一种命令式编程,由开发者决定何时去访问序列中的next()元素。而在响应式流中,与Iterable-Iterator对应的是Publisher-Subscriber。当新的可用元素出现时,发布者通知订阅者,这种”推“正是响应的关键。此外,应用于推入元素上的操作是声明式的而不是命令式的:程序员要做的是表达计算的逻辑,而不是描述精准的控制流程。

    除了推送元素,响应式编程还定义了良好的错误处理和完成通知方式。发布者可以通过调用next()方法推送新的元素给订阅者,也可以通过调用onError()方法发送一个错误信号或者调用onComplete()发送一个完成信号。错误信号和完成信号都会终止序列。

    响应式编程非常灵活,它支持没有值、一个值或n个值的用例(包括无限序列,例如时钟的连续滴答声)。

    但是让我们首先考虑,为什么我们需要响应式编程?

    阻塞可能是浪费的

    现代的应用需要满足大量的用户并发访问,尽管硬件的能力依然在不断提高,软件的性能仍然是一个关键问题。

    通常有两种方法可以提升程序的性能:

    并行化:使用更多的线程和更多的硬件资源。

    在如何使用现有资源方面寻求更高的效率。

    通常,Java开发者使用阻塞代码编程。在出现性能瓶颈之前,这种做法没有问题,此时就需要引入额外的线程,来运行相似的阻塞代码。但是,这种资源利用率的扩展可能很快引来争用和并发问题。

    更糟糕的是,阻塞会浪费资源。如果仔细观察,只要程序涉及一些延迟(特别是IO,比如数据库请求或网络调用),资源就会被浪费,因为一个(或多个线程)正处于空闲状态,等待数据。

    所以,并行化方法并不是什么灵丹妙药。为了提高硬件资源的利用率,响应式编程是必要的。但是它也很复杂,因为容易造成资源浪费。

    使用异步来解决?

    上文提到的第二种方法是寻求更高的效率,可以解决资源浪费问题。通过编写异步非阻塞代码,你可以将执行切换到另一个使用相同底层资源的活动任务上,在异步执行完成后返回到当前程序。

    如何在JVM上编写异步代码呢?Java提供了两种异步编程模型:

    Callbacks:异步方法没有返回值,但是提供一个额外的回调参数(一个lambda或者匿名类对象),当结果可用时调用该参数。

    Futures:异步方法立即返回一个Future对象。异步线程计算一个T值,Future对象封装对它的访问。该值不是立即可用的,但可以轮询Future对象,直到该值可用为止。

    这些技术足够好吗?并不是每个场景都适用,而且两种方式都有限制。

    回调是很难组合在一起的,很快就会导致难以阅读和维护的代码(称为”回调地狱”)。

    Futures比回调稍微好一点,但是在组合方面依然做的不够好,即使Java 8中引入了CompletableFuture。把多个Future编排到一起虽然可行,但是并不容易。而且,Future还有另外的问题:通过调用get()方法,很容易让Future对象进入到另一种阻塞情景;它们不支持延时计算;不支持多值和高级错误处理。

    从命令式编程到响应式编程

    响应式编程库Reactor旨在解决JVM上这些”经典“的异步编程方式的缺点,同时关注几个额外的方面:

    可组合性和可读性;

    使用丰富的操作符词汇操作数据流;

    在订阅数据流之前什么也不会发生(延时计算);

    背压(backpressure)或消费者向生产者发送发射速率过快的信号的能力;

    与并发无关的高级抽象;

    可组合性和可读性

    可组合性指的是编排、协调多个异步任务的能力。包括使用先前任务的结果为后续任务提供输入,或者以fork-join格式执行多个任务以及在更高级别的系统中将异步任务作为独立组件重用。

    编排任务的能力与代码的可读性和可维护性是紧密相关的。随着异步处理的数量和复杂性的增加,编写和阅读代码变得越来越困难。正如我们所看到的,回调模式很简单,但是它的一个主要缺点是,对于复杂的任务,你需要从回调中执行回调,回调本身嵌套在另一个回调中,等等。这种混乱被称为回调地狱。可以想象,这样的代码很难回头进行推理。

    装配线类比

    你可以将在响应式应用中处理数据想象成(数据)在装配线上移动。响应式编程既是传送带,又是工作站。原材料从一个数据源(最初的Publisher)中倾泻而出,最终成为准备推送给消费者(Subscriber)的成品。

    原材料可以经过各种转换和中间步骤,或者成为将中间部件组装在一起的大型装配线的一部分。如果在某一点发生了小故障或者阻塞,受影响的工作站可以向上游发信号限制原材料的流动。

    操作符

    操作符就是装配线中的工作站。每一个操作符都向发布者(Publisher)添加新的行为,并将上一步中的发布者包装到一个新的实例中。整个链条就是这样连接在一起的,数据源自第一个发布者(Publisher),并沿着链条向下移动,在每个链接处被转换。最终,一个订阅者(Subscriber)结束这个流程。

    订阅前什么也不会发生

    在Reactor中,当你编写一个发布者(Publiser)链时,默认情况下不会开始向其中注入数据。相反,你创建的是一个异步处理的抽象描述(这有助于重用性和组合)。

    通过订阅操作(Subscribe()),你将发布者绑定到一个订阅者,从而触发整个链中的数据流。这是通过订阅者(Subscriber)发出一个请求(request())信号在内部实现的,该信号向上游传播,一直到源头发布者(Publisher)。

    背压

    向上游传播信号也被用来实现背压。在装配线类比中,我们将其描述为当工作站处理速度比上游工作站慢时,沿装配线向上游发送的反馈信号。

    响应式流规范中定义的真正的机制与装配线类比非常相似:订阅者可以在无限制的模式下工作,并让数据源以最快的速度推送数据;或者它可以用request()机制向数据源发出信号,表明自己最多可以处理n个数据。

    中间操作符还可以在传输过程中更改请求。假设有一个buffer操作符以10个元素为一个批次对元素进行分组。如果订阅者请求1个缓冲区,数据源就要生成10个元素。一些操作符还实现预取(prefetching)策略,这避免了每次请求一个元素的往返开销,如果在被请求之前生成元素的成本不是太高,那么这样做是有益的。

    预取策略将推模型转换为推-拉混合模型,在这种混合模型中,下游可以从上游拉出n个元素(如果它们已经可用)。但是,如果元素还没有准备好,它们会在生产好之后由上游推给下游。

    热响应式序列和冷响应式序列

    在响应式编程库的Rx家族中,我们可以区分两大类响应式序列:热响应式序列和冷响应式序列 。这种区别主要与响应式流如何对订阅者做出响应有关:

    冷响应式序列对每个订阅者(包括数据源)会重新启动一个全新的响应序列。

    热响应式序列对每个订阅者不会重零开始。相反,后来的订阅者只能接收到他们订阅后发出的数据。但是,请注意,一些热响应式序列可以缓存或回放全部或部分数据发布历史。从一般的角度来看,热序列甚至可以在没有订阅者在监听时发射数据(“订阅前什么都不会发生”规则的一个例外)。

    更多相关内容
  • 一文搞懂: 响应式编程什么?

    千次阅读 2021-01-10 16:09:15
    当一个系统具有即时响应性(Responsive)、回弹性(Resilient)、弹性(Elastic)以及消息驱动(Message Driven)。 我们称这样的系统为反应系统(Reactive System)。

    导言

    目前响应式编程还属于大乱斗的时代,各家有各家的定义和实现。响应式宣言定义了响应式应该满足的特性。Spring5有自己的响应式方案技术栈、广为人知的rxJava所属的reactiveX也有自己的理解。有的说响应式是基于事件的,有的说是基于数据流的。有人说响应式编程其实只是个便于并发编程的api库,有人说响应式编程是一种编程思想、一种编程范型。技术圈子里各有各的说法,正如小马哥所说: 一种技术,各自表述。响应式编程含糊不清、有些空洞无实、有些夸大其词,难免让普通学习者难以入门,十分头痛。
    在这里插入图片描述

    以下主要基于响应式宣言和reactiveX来解释响应式编程,所说观点如有异议欢迎评论区讨论指教。

    响应式编程是什么?

    是一款使用异步数据流编程的响应式编程思想。(from here)

    Reactive Programming是基于观察者模型的这是大家的共识,它提供了非阻塞、异步的特性,便于处理异步情景,从而避免回调地狱和突破Future的局限性: 难以组合。(这个优点我并不认同,java8 的completeFuture已经有很好的改善了)。

    在reactiveX中,对好几个语言提供了api库,该库扩展了观察者模式,支持基于数据事件的驱动。允许声明式组合观察者序列。将同步编程和异步编程抽象成统一。

    异步编程和同步编程在代码中的体现可以如下表体现。不同于传统的pull型的,响应式编程通常采用push型的数据传输,由生产者控制消费速度,但当消费者消费速度不足时,也能反过来提示生产者降低生产速度(backPressure),backPressure将在下文阐述。

    响应式编程通常带有函数式的思想,但这是用来简化面向对象语法臃肿性的,响应式编程的核心在于Reactive,只是带有了部分的Functional的特性,更不是Functional Reactive Programming,这是完全不同的东西。

    响应式宣言

    响应式系统应该具有什么特性

    当一个系统具有即时响应性(Responsive)、回弹性(Resilient)、弹性(Elastic)以及消息驱动(Message Driven)。 我们称这样的系统为反应式系统(Reactive System)。

    img

    如图所示,响应式编程的价值在于1. 易于编写维护(特别是异步编程) 2. 及时响应。

    什么是及时响应? 不管是出了什么问题,或断电、或网络抖动、或代码bug,都能及时做出响应,提供足够的可用性。

    响应式宣言中将异常也看做一种消息来简化对错误的处理,其中错误可以简单看成看做各种Exception。

    什么是弹性? 回弹性?

    弹性是指不同负载下,系统的吞吐量、响应性没有什么区别。响应式通过伸缩算法,使系统没有资源争用点和中央瓶颈。

    回弹性是指当系统出现失败时,例如断电、硬件故障、资源耗尽等情况,通过复制、 遏制、 隔离以及委托来保证依然具有即时响应性。

    消息驱动还是事件驱动?

    消息时数据,事件是信号。消息的包含的信息量更丰富。

    对于消息驱动的系统,如果没有消息来,接受者异步非阻塞等待线程到来(休眠)。

    对于事件驱动的系统,常使用回调的方法实现。

    响应式宣言中推荐使用消息驱动来设计,因为事件驱动的系统很难实现回弹性:

    对于事件驱动系统,当处理过程已经就绪,监听器已经设置好, 以便于响应结果并对结果进行变换时, 这些监听器通常都将直接地处理成功或者失败, 并向原始的客户端报告执行结果。

    (这些监听器)响应组件的失败, 以便于恢复它(指失败的组件)的正常功能,而在另外一方面, 需要处理的是那些并没有与短暂的客户端请求捆绑在一起的, 但是影响了整个组件的健康状况的失败。

    响应式编程相关讨论

    为什么使用观察者模式

    使用该模式将开发者从回调地狱中解放出来。ReactiveX使用流来将异步、同步统一起来,并通过订阅者来将各种操作组合起来,提高了代码易读性。

    什么是回调地狱? 其实就是当回调函数需要组合时,常常会使代码复杂度大大增加,代码易读性大大减少的一种现象。

    ReactiveX的观察者模式是十分灵活的,不仅仅支持push、还支持pull。

    什么是背压(backPressure)

    其实背压翻译得不好,叫回压更合适。知乎上有个背压的解说我觉得说得特别好,背压其实只是一种现象,但响应式编程中的backPressure其实是一种解决消费赶不上生产时的一种处理策略,有让生产者慢点push的, 有直接丢弃的…

    在数据流从上游生产者向下游消费者传输的过程中,上游生产速度大于下游消费速度,导致下游的 Buffer 溢出,这种现象就叫做 Backpressure 。

    个人感想

    在我还是学生的时候就常听人说什么响应式编程、什么webflux,当时被各种新技术迷乱了眼睛的我胡乱的看了几个相关介绍,再听响应式编程被各种各样的博客文章吹嘘的多么多么好、多么多么先进,当时就觉得自己一定要学习。

    工作后才发现实际的生产其实不需要多么牛逼先进的东西,大多时候线上系统稳定性才是最重要的。国内java8还是大行其道,好多人java8的特性还没摸透,但依然在工作岗位上支撑起了一片天。

    对于开发者,不要被互联网上各式各样的“技术营销”骗了,在学习新技术前多想想是不是真的要学。什么代码、什么技术、什么框架,都只是互联网农民工手里的锤子罢了,不是越新越花哨的锤子就越好,能解决实际工作内容的就行。

    有时候与其学着各种各样的新技术新框架,不如陪家人看看电影、逛逛街,我们不是互联网的螺丝钉,我们是人,只是恰逢资本主义时代,大多数人都被异化成了“工具人”,但我们都有自己的生活,自己的家人、朋友,每个人都不一样,都有自己的人生。

    总而言之,目前市场上响应式编程各种各样,定义五花八门,优点论证也不够充足,大家还是不要急着入场。
    在这里插入图片描述

    相关文档

    1. http://reactivex.io/intro.html
    2. GitHub rxjava
    3. Reactive-Programming-一种技术-各自表述
    4. 响应式宣言
    5. 响应式宣言词汇表
    展开全文
  • 什么是函数响应式编程响应式编程思想为体,函数式编程思想为。首先我们要来了解什么是函数式编程和响应式编程什么是函数式编程? 顾名思义,函数式编程就是函数来解决问题的编程方式,几乎任何一门语言都...

    什么是函数响应式编程?

    响应式编程思想为体,函数式编程思想为用。首先我们要来了解什么是函数式编程和响应式编程。

    什么是函数式编程?

    顾名思义,函数式编程就是用函数来解决问题的编程方式,几乎任何一门语言都支持函数,但是函数式编程具有几个特点:

    1. 声明式(Declarative)
    2. 纯函数(Pure Functio
    3. 数据不可变性(Immutability)

    在深入了解这三个特点之前,我们需要知道JavaScript是不是函数式编程?
    有的语言生来就是函数式编程,比如Haskell、Lisp这些语言本身就强制要求代码遵从以上三个要求,不过JavaScript并没有要求数据不可变性,也就是说用JavaScript写的函数并不能保证没有任何副作用。

    那到底javascript是不是函数式编程?

    从语言的角度上讲,JavaScript当然不算一个纯粹意义上的函数式编程语言,但是JavaScript的函数具有第一公民的身份,因为函数本身就是一个对象,可以被赋值给一个对象也可以作为一个参数传递,可以很方便的应用函数式编程的思想。虽然javascript不算纯粹意义上的编程语言,但是通过一些编程规范和一点工具的帮助,完全可以写出函数式的代码。
    RxJs就是辅助我们写出函数式代码的一种工具。 接下来,我们分别介绍JavaScript如何满⾜函数式编程的特性需要。

    声明式

    和声明式相对应的编程⽅式叫做命令式编程(ImperativeProgramming),命令式编程也是最常见的⼀种编程⽅式。
    先来看⼀个命令式编程的例⼦,我们要实现⼀个功能,将⼀个数组中每个元素的数值乘以2。这样⼀个功能可以实现为⼀个叫double的函数,代码如下:

    function double(arr) {
    	const results = [];
    	for (let i = 0; i < arr.length; i++){
    		results.push(arr[i] * 2)
    	};
    	return results;
    };
    

    代码解析:

    1. 创建一个results的空数组
    2. 创建一个for循环,然后每次把arr数组的每一个值乘2,再利用push方法添加到results中
    3. 返回 results数组

    这个double函数的实现没有任何⽑病,但是,我们又来了⼀个需求:实现⼀个函数,能够把⼀个数组的每个元素加⼀。
    我们可以再实现⼀个addOne函数,代码如下:

    function addOne(arr) {
    	const results = [];
    	for (let i = 0; i < arr.length; i++){
    		results.push(arr[i] + 1)
    	};
    	return results;
    };
    

    如果比较一下double和addOne这两个函数,发现除了函数名和push的参数不一样之外其余的代码都是一样的,这就出现了重复代码的问题(重复代码可能是所有软件中邪恶的根源)。我们可以看到命令式编程的一个大问题,我们应该改进它。
    我们可以利用javascript中的map方法,来重新实现double和addOne函数,代码如下:

    function double(arr) {
    	return arr.map(function(item) {return item * 2});
    }
    function addOne(arr) {
    	return arr.map(function(item) {return item + 1});
    }
    

    代码简洁了很多,没有了for循环,也没有了push的操作,因为这些都被封装在map方法里了。map方法主要提供了一个函数类型的参数,这个参数可以定制对每一个数组元素如何处理。我们来看double中这个定制函数,代码如下:

    function(item) {return item * 2};
    

    这个函数实现了这样⼀个功能:不管传⼊什么数据,都会返回这个数据乘以2的结果。这就是声明式编程,因为在double函数中,代码实际上是这样⼀种解读:把⼀个数组映射(map)为另⼀个数组,每个对应的元素都乘以2。我们可以把上面的代码通过es6的箭头函数再做一次优化,代码如下:

    const double = arr => arr.map(item => item * 2);
    const addOne = arr => arr.map(item => item + 1);
    

    这就是声明式编程,比命令是编程更容易维护,代码更简洁,更具有表现力。

    纯函数

    还是以上面提到的double函数为例,这一次我们从使用者的角度来看,代码如下:

    const oneArray = [1, 2, 3];
    const anotherArray = double(oneArray);
    // anotherArray的内容为[ 2, 4, 6 ]
    // oneArray的内容依然是[ 1, 2, 3 ]
    

    我们先声明⼀个oneArray,然后将oneArray作为参数传递给double函数,返回的结果赋值给anotherArray,因为double的实现遵从了保持数据不可改的原则,所以oneArray数组依然是以前的值,⽽anotherArray是⼀个全新的数组,这两个数组中的数据相互独⽴,互不⼲扰。所以,我们说double就是⼀个“纯函数”。所谓纯函数,指的是满⾜下⾯两个条件的函数:

    • 函数的执行过程由参数决定,不会受除参数之外的任何数据影响。
    • 函数不会修改任何外部状态,比如修改全局变量传入的参数。

    表⾯上看起来,纯函数的要求,是限制了我们编写函数的⽅式,似乎让我们没法写出“更强⼤”的函数,实际上,这种限制带来的好处远远⼤于所谓“更强⼤”的好处,因为,纯函数让我们的代码更加简单,从⽽更加容易维护,更加不容易产⽣bug。
    我们还是通过⼀段代码例⼦来说明,代码如下:

    function arrayPush (arr, newValue) {
    	arr.push(newValue);
    	return arr;
    }
    const originalArray = [1, 2, 3];
    const pushedArray = arrayPush(originalArray, 4);
    const doubledPushedArray = double(pushedArray);
    // pushedArray值应该是[ 1, 2, 3, 4 ]
    // doubledPushedArray值应该是 [ 2, 4, 6, 8 ]
    

    当上⾯的语句执⾏完毕之后,originalArray的值却是[ 1, 2, 3, 4 ],为什么呢,因为push的操作会改变原数组,也就是说会改变传递过来的参数originalArray 数组。而函数式编程的一个条件就是不能修改任何外部状态,所以我们不能使用push操作,但是我们可以用es6的扩展运算符…达到纯函数的条件 ,代码如下:

    function arrayPush (arr, newValue) {
    	return [...arr, newValue];
    }
    

    和纯函数相反的就是“不纯函数”(Impure Function),⼀个函数之所以不纯,可能做了下⾯这些事情:

    • 改变全局变量的值。
    • 改变输⼊参数引⽤的对象,就像上⾯不是纯函数的arrayPush实现。
    • 读取⽤户输⼊,⽐如调⽤了alert或者confirm函数。
    • 抛出⼀个异常。
    • 操作浏览器的DOM。
    • ⽹络输⼊/输出操作,⽐如通过AJAX调⽤⼀个服务器的API。

    上⾯还只是不纯函数的⼀部分表现,其实,有⼀个很简单的判断函数纯不纯的⽅法,就是假设将⼀个函数调⽤替换为⼀个预期返回的常数,程序运⾏结果是否一样。

    数据不可变

    程序要好发挥作⽤当然是要产⽣变化的数据,但是并不意味着必须要去修改现有数据,替换⽅法是通过产⽣新的数据,来实现这种“变化”,也就是说,当我们需要数据状态发⽣改变时,保持原有数据不变,产⽣⼀个新的数据来体现这种变化。
    不可改变的数据就是Immutable数据,它⼀旦产⽣,我们就可以肯定它的值永远不会变,这⾮常有利于代码的理解。在JavaScript中,字符串类型、数字类型就是不可改变的数据,使⽤这两种类型的数据给你带来的⿇烦⽐较少。相反,JavaScript中⼤部分对象都是可变的,⽐如JavaScript⾃带的原⽣数组类型,数组的push、pop、sort函数都会改变⼀个数组的内容,由此引发的bug可不少。这些不纯的函数导致JavaScript天⽣不是⼀个纯粹意义上的函数式编程语⾔。

    什么是响应式编程?

    响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。如果你使⽤过Excel的公式功能,你就已经应⽤过响应式编程。再例如,在命令式编程环境中,a:=b+c表示将表达式的结果赋给a,而之后改变b或c的值不会影响a。但在响应式编程中,a的值会随着b或c的更新而更新。
      响应式编程最初是为了简化交互式用户界面的创建和实时系统动画的绘制而提出来的一种方法,但它本质上是一种通用的编程范式。例如,在MVC软件架构中,响应式编程允许将相关模型的变化自动反映到视图上,反之亦然。

    展开全文
  • (1)什么响应式编程——响应式Spring的道法术器

    万次阅读 多人点赞 2018-03-06 10:54:22
    1.1 什么响应式编程? 在开始讨论响应式编程(Reactive Programming)之前,先来看一个我们经常使用的一款堪称“响应式典范”的强大的生产力工具——电子表格。 举个简单的例子,某电商网站正在搞促销活动,任何...

    本系列其他文章见:《响应式Spring的道法术器》

    响应式编程之道

    1.1 什么是响应式编程?

    在开始讨论响应式编程(Reactive Programming)之前,先来看一个我们经常使用的一款堪称“响应式典范”的强大的生产力工具——电子表格。

    举个简单的例子,某电商网站正在搞促销活动,任何单品都可以参加“满199减40”的活动,而且“满500包邮”。吃货小明有选择障碍(当然主要原因还是一个字:穷),他有个习惯,就是先在Excel上根据预算算好自己要买的东西:

    相信大家都用过Excel中的公式,这是一个统计购物车商品和订单应付金额的表格,其中涉及到一些公式:

    上图中蓝色的线是公式的引用关系,从中可以看出,“商品金额”是通过“单价x数量”得到的,“满199减40”会判断该商品金额是否满199并根据情况减掉40,右侧“订单总金额”是“满199减40”这一列的和,“邮费”会根据订单总金额计算,“最终应付款”就是订单总金额加上邮费。

    1.1.1 变化传递(propagation of change)

    为什么说电子表格软件是“响应式典范”呢,因为“单价”和“数量”的任何变动,都会被引用(“监听”)它的单元格实时更新计算结果,如果还有图表或数据透视图引用了这块数据,那么也会相应变化,做到了实时响应。变化的时候甚至还有动画效果,用户体验一级棒!

    这是响应式的核心特点之一:变化传递(propagation of change)。一个单元格变化之后,会像多米诺骨牌一样,导致直接和间接引用它的其他单元格均发生相应变化。

    看到这里,你可能会说,“切~ 不就是算付款金额吗,购物网站上都有这个最基础不过的功能啊”,这就“响应式”啦?但凡一个与用户交互的系统都得“响应”用户交互啊

    但是在响应式编程中,基于“变化传递”的特点,触发响应的主体发生了变化。假设购物车管理和订单付款是两个不同的模块,或者至少是两个不同的类——CartInvoice。也许我们的代码是这样的:

    Product.java(假设商品有两个属性nameprice,简单起见,price就不用BigDecimal类型了)

    public class Product {
        private String name;
        private double price;
        // 构造方法、getters、setters
    }
    

    Cart模块中:

    import com.example.Invoice; // 2
    
    public class Cart {
        ...
        public boolean addProduct(Product product, int quantity) {
            ...
            double figure = product.getPrice() * quantity;
            invoice.update(figure); // 1
            ...
        }
        ...
    }
    
    1. 是由Cart的对象去调用Invoice对象的更新订单金额的方法;
    2. Cart的代码中需要import Invoice

    而我们再观察这个Excel,发现“订单总金额”的计算公式不仅位于自己的单元格中,而且这个公式能主动监听和响应购物车数据的变化事件。对于购物车来说,它没有对订单付款方面的任何公式引用。感觉就像这样:

    假设数据流有操作的商品product和变化个数quantity两个属性:

    public class CartEvent {
        private Product product;
        private int quantity;
        // 构造方法、getters、setters
    }
    

    Invoice模块中:

    import com.example.Cart // 2
    
    public class Invoice {
        ...
        public Invoice(Cart cart) {
            ...
            this.listenOn(cart);    // 1
            ...
        }
        // 回调方法
        public void onCartChange(CartEvent event) {
            ...
        }
        ...
    }
    
    1. 是由Invoice的对象在初始化的时候就声明了对Cart对象的监听,从而一旦Cart对象有响应的事件(比如添加商品)发生的时候,Invoice就会响应;
    2. Invoice的代码中import Cart

    做过Java桌面开发的朋友可能会想到Java swing中的各种监听器,比如MouseListener能够监听鼠标的操作,并实时做出响应。所以C/S的客户端总是比B/S的Web界面更具有响应性嘛。

    所以,这里我们说的是一种生产者只负责生成并发出数据/事件,消费者来监听并负责定义如何处理数据/事件的变化传递方式

    那么,Cart对象如何在发生变化的时候“发出”数据或事件呢?

    1.1.2 数据流(data stream)

    这些数据/事件在响应式编程里会以数据流的形式发出。

    我们再观察一下购物车,这里有若干商品,小明每次往购物车里添加或移除一种商品,或调整商品的购买数量,这种事件都会像过电一样流过这由公式串起来的多米诺骨牌一次。这一次一次的操作事件连起来就是一串数据流(data stream),如果我们能够及时对数据流的每一个事件做出响应,会有效提高系统的响应水平。这是响应式的另一个核心特点:基于数据流(data stream)

    如下图是小明选购商品的过程,为了既不超预算,又能省邮费,有时加有时减:

    这一次一次的操作就构成了一串数据流。Invoice模块中的代码可能是这样:

        public Invoice(Cart cart) {
            ...
            this.listenOn(cart.eventStream());  // 1
            ...
        }
    
    1. 其中,cart.eventStream()是要监听的购物车的操作事件数据流,listenOn方法能够对数据流中到来的元素依次进行处理。

    1.1.3 声明式(declarative)

    我们再到listenOn方法去看一下:

    Invoice模块中,上边的一串公式被组装成如下的伪代码:

        public void listenOn(DataStream<CartEvent> cartEventStream) {   // 1
            double sum = 0;
            double total = cartEventStream
                // 分别计算商品金额
                .map(cartEvent -> cartEvent.getProduct().getPrice() * cartEvent.getQuantity())  // 2
                // 计算满减后的商品金额
                .map(v -> (v > 199) ? (v - 40) : v)
                // 将金额的变化累加到sum
                .map(v -> {sum += v; return sum;})
                // 根据sum判断是否免邮,得到最终总付款金额
                .map(sum -> (sum > 500) ? sum : (sum + 50));
            ...
    
    1. cartEventStream是数据流,DataStream是某种数据流类型,可以暂时想象成类似在Java 8版本增加的对数据流进行处理的Stream API(下节会说到为啥不用Java Stream)。

    2. map方法用于对数据流中的元素进行映射,比如第一个将cartEvent中的商品价格和数量拿到,然后算出本次操作的金额;第二个判断是否能享受“满199减40”的活动。

    这里的伪代码用到了lambda,它非常适用于数据流的处理。没有接触过lambda的话没有关系,我们后续会再聊到它。

    这是一种**“声明式(declarative)”**的编程范式。通过四个串起来的map调用,我们先声明好了对于数据流“将会”进行什么样的处理,当有数据流过来时,就会按照声明好的处理流程逐个进行处理。

    比如对于第一个map操作:

    **声明式编程范式的威力在于以不变应万变。**无论到来的元素是什么,计算逻辑是不变的,从而形成了一种对计算逻辑的“绑定”。

    再举个简单的例子方便理解:

    a = 1;
    b = a + 1;
    a = 2;
    

    这个时候,b是多少呢?在Java以及多数语言中,b的结果是2,第二次对a的赋值并不会影响b的值。

    假设Java引入了一种新的赋值方式:=,表示一种对a的绑定关系,如

    a = 1;
    b := a + 1;
    a = 2;
    

    由于b保存的不是某次计算的值,而是针对a的一种绑定关系,所以b能够随时根据a的值的变化而变化,这时候b==3,我们就可以说:=是一种声明式赋值方式。而普通的=是一种命令式赋值方式。事实上,我们绝大多数的开发都是命令式的,如果需要用命令式编程表达类似上边的这种绑定关系,在每次a发生变化并需要拿到b的时候都得执行b = a + 1来更新b的值。

    如此想来,“绑定美元政策”不也是一种声明式的范式吗~

    总结来说,命令式是面向过程的,声明式是面向结构的

    不过命令式和声明式本身并无高低之分,只是声明式比较适合基于流的处理方式。这是响应式的第三个核心特点:声明式(declarative)。结合“变化传递”的特点,声明式能够让基于数据流的开发更加友好。

    1.1.4 总结

    总结起来,响应式编程(reactive programming)是一种基于数据流(data stream)和变化传递(propagation of change)的声明式(declarative)的编程范式。

    响应式编程的“变化传递”就相当于果汁流水线的管道;在入口放进橙子,出来的就是橙汁;放西瓜,出来的就是西瓜汁,橙子和西瓜、以及机器中的果肉果汁以及残渣等,都是流动的“数据流”;管道的图纸是用“声明式”的语言表示的。

    这种编程范式如何让Web应用更加“reactive”呢?

    我们设想这样一种场景,我们从底层数据库驱动,经过持久层、服务层、MVC层中的model,到用户的前端界面的元素,全部都采用声明式的编程范式,从而搭建一条能够传递变化的管道,这样我们只要更新一下数据库中的数据,用户的界面上就相应的发生变化,岂不美哉?尤其重要的是,一处发生变化,我们不需要各种命令式的调用来传递这种变化,而是由搭建好的“流水线”自动传递。

    这种场景用在哪呢?比如一个日志监控系统,我们的前端页面将不再需要通过“命令式”的轮询的方式不断向服务器请求数据然后进行更新,而是在建立好通道之后,数据流从系统源源不断流向页面,从而展现实时的指标变化曲线;再比如一个社交平台,朋友的动态、点赞和留言不是手动刷出来的,而是当后台数据变化的时候自动体现到界面上的。

    具体如何来实现呢,请看下一节关于响应式流的介绍。

    展开全文
  • 未来的趋势,什么响应式编程

    千次阅读 热门讨论 2021-12-14 19:53:35
    Spring5 Webflux 前言 ★ 这里是小冷的博客 ✓ 优质技术好文见专栏 个人公众号,分享一些技术上的文章,以及遇到的坑 当前系列:Spring5 Webflux 系列 源代码 git 仓库 ‘ Reactor代码地址 代码Git 仓库地址 ...
  • RsJava 响应式编程, 高清带目录. 响应式编程是一种基于异步数据流概念的编程模式。在开发手机 App、 Web App 时, 要想保证对用户 请求的实时响应,给用户带来流畅的体验,响应式编程是一个不错的选择, RxJava 则是...
  • Java响应式编程框架

    2021-04-07 11:49:24
    响应式,直白点理解,就是当数据准备好了,再通知我(函数),有时会觉得与回调很像。 代表框架:Spring Webflux、Vert.x、quack
  • 什么响应式编程? 如果要直接理解,可能比较抽象,但我们可以引用百度百科的例子;例如:a = b + c; 这句代码将b+c的值赋给a,而之后如果b和c的值改变了不会影响到a,然而,对于响应式编程,之后b和c的值的改变也...
  • SpringBoot2.0响应式编程 一、SpringBoot2.0 响应式编程基础知识 Spring WebFlux官方文档 SpringBoot WebFlux文档 1.什么是Spring WebFlux? 在了解Spring WebFlux之前,我们先来对比说说什么是Spring MVC,这更...
  • 响应式编程在Android中的应用

    千次阅读 2020-08-13 01:46:20
    响应式编程简介 响应式编程的具体实现-RxJava 基本概念 RxJava的四种角色 热Observable和冷Observable Observable创建符 Subject 直接创建 Observable 从列表创建 Observable 具有特殊功能的创建符 过滤Observables ...
  • SpringBoot响应式编程(整理一)

    千次阅读 2019-09-11 15:11:07
    1、SprinBoot响应式编程简介 基础理解: 依赖于事件,事件驱动(Event-driven) 一系列事件称为“流” 异步 非阻塞 观察者模式 (1)Spring WebFlux是Spring Framework 5.0中引入的新的反应式Web框架。 与Spring MVC...
  • Java:理解java响应式编程

    千次阅读 2021-01-15 14:36:41
    理解java响应式编程
  • java reactor 响应式编程

    2022-01-14 11:17:12
    响应式编程就是基于reactor的思想,当你做一个带一定延迟的才能够返回的io操作时,不会阻塞,而是立刻返回一个流,并且订阅这个流,当这个流上产生了返回数据,可以立刻得到通知并调用回调函数处理数据。...
  • 响应式编程(reactive programming)是一种基于数据流(data stream)和变化传递(propagation of change)的声明式(declarative)的编程范式。 响应式编程会声明数据变化对应的操作逻辑(声明式),会监听数据...
  • Flutter的响应式编程

    2019-09-05 20:23:44
    我们可能都听说过响应式编程这个名词,但什么响应式编程呢?我们先看看维基百科上的定义: 这里面很多的名词,像declarative programming paradigm(声明式编程范式),imperative programming paradigm(指令式...
  • Rxjava响应式编程

    千次阅读 2021-12-15 18:07:29
    RxJava的具体思想、一些操作符的使用,响应式编程的思想
  • 函数式编程 防御式编程 响应式编程 契约式编程
  • Reactor响应式编程介绍-1 (学习笔记 2021.11.5) 前言: (中文文档) (官网) 本文档的一些典型的名词如下:Publisher(发布者)、Subscriber(订阅者)、Subscription(订阅 n.)、subscribe(订阅 v.)。 event/...
  • Java中的响应式编程浅析

    万次阅读 2018-06-24 09:50:45
    响应式编程是一种编程概念,在很多编程语言中都应用。其中,在Java中,我们比较熟悉的RxJava,有关RxJava的介绍,已经大神写出比较完善的介绍: 深入浅出RxJava(一:基础篇) 深入浅出RxJava(二:操作符) ...
  • 函数响应式编程

    千次阅读 2016-08-18 14:04:29
    原文链接 : The introduction to Reactive Programming you've been missing作者 : @...相信你们在学习响应式编程这个新技术的时候都会充满了好奇,特别是它的一些变体,例如:Rx系列、Bacon.js、RAC等等……
  • 响应式编程简介之:Reactor

    万次阅读 2020-11-06 09:12:47
    其中Flux 代表的是 0 to N 个响应式序列,而Mono代表的是0或者1个响应式序列。 我们看一个Flux是怎么transfer items的: 先看下Flux的定义: public abstract class Flux<T> implements Publisher<T> 可以看到Flux...
  • 什么响应式编程,Java 如何实现

    万次阅读 2021-02-06 08:21:18
    我们这里通过唯一 id 获取知乎的某个回答作为例子,首先我们先明确下,一次HTTP请求到服务器上处理完之后,将响应写回这次请求的连接,就是完成这次请求了,如下: public void request(Connection connection, ...
  • 现在, Java 的各种基于 Reactor 模型的响应式编程库或者框架越来越多了,像是 RxJava,Project Reactor,Vert.x 等等等等。在 Java 9, Java 也引入了自己的 响应式编程的一种标准接口,即java.util.concurrent....
  • Rxswift学习之(一)函数响应式编程思想1. 函数响应式编程思想简介 1. 函数响应式编程思想简介 维基百科对函数式编程的解释 在计算机科学里,函数式编程是一种编程范式,它将计算描述为表达式求值并避免了...
  • Vue响应式编程

    千次阅读 2019-09-11 18:49:18
    上面这个例子展示的是Vue的响应式能力,的是数值,也可以对 v-bind 的属性进行操作: <!DOCTYPE html> < html lang = " en " > < head > < meta charset = " UTF-8 " > < meta name = " ...
  • 浅谈Spring5 响应式编程

    千次阅读 2018-06-29 17:18:24
    目录为什么响应式编程 用于响应式编程实现的理想案例响应流 (Reactive Streams) Spring 5 提供的响应式编程 Spring Web Reactive vs. Spring Web MVC响应式编程的基本结构Reactive Web ClientSpring 5 的局限...
  • 响应式编程规范

    千次阅读 2019-02-01 14:40:51
    响应式编程的概念已经很早就提出来了,业内很多大牛共同构建了 响应式宣言(中文版)。如果您认可该宣言,可以在其中签下自己的大名。 内容不多,这里我们直接拷贝过来。 在不同领域中深耕的组织都在不约而同地尝试...
  • ========================15...1、SprinBoot2.x响应式编程简介 简介:讲解什么是reactive响应式编程和使用的好处 1、基础理解: 依赖于事件,事件驱动(Event-driven) 一系列事件称为“流” 异步 非阻塞 观察...
  • 响应式编程的首要问题 - 不好调试 我们在分析传统代码的时候,在哪里打了断点,就能看到直观的调用堆栈,来搞清楚,谁调用了这个代码,之前对参数做了什么修改,等等。但是在响应式编程中,这个问题就很麻烦。来看...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 156,493
精华内容 62,597
关键字:

响应式编程有什么用