精华内容
下载资源
问答
  • (1)什么响应式编程——响应式Spring的道法术器

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

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

    响应式编程之道

    1.1 什么是响应式编程?

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

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

    RX9VjUmQltKah38.jpg

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

    q4p6hzMXQfYPurA.jpg

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

    1.1.1 变化传递(propagation of change)

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

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

    Ex2SfmLoNg7TOuw.jpg

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

    但是在响应式编程中,基于“变化传递”的特点,触发响应的主体发生了变化。假设购物车管理和订单付款是两个不同的模块,或者至少是两个不同的类——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操作:

    olW5mzADdxHt6nS.jpg

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

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

    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,到用户的前端界面的元素,全部都采用声明式的编程范式,从而搭建一条能够传递变化的管道,这样我们只要更新一下数据库中的数据,用户的界面上就相应的发生变化,岂不美哉?尤其重要的是,一处发生变化,我们不需要各种命令式的调用来传递这种变化,而是由搭建好的“流水线”自动传递。

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

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

    展开全文
  • 什么是函数响应式编程响应式编程思想为体,函数式编程思想为。首先我们要来了解什么是函数式编程和响应式编程什么是函数式编程? 顾名思义,函数式编程就是函数来解决问题的编程方式,几乎任何一门语言都...

    什么是函数响应式编程?

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

    什么是函数式编程?

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

    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软件架构中,响应式编程允许将相关模型的变化自动反映到视图上,反之亦然。

    展开全文
  • 响应式编程规范

    千次阅读 2019-02-01 14:40:51
    响应式编程的概念已经很早就提出来了,业内很多大牛共同构建了 响应式宣言(中文版)。如果您认可该宣言,可以在其中签下自己的大名。 内容不多,这里我们直接拷贝过来。 在不同领域中深耕的组织都在不约而同地尝试...

    响应式编程系列文章

    响应式宣言

    响应式编程的概念已经很早就提出来了,业内很多大牛共同构建了 响应式宣言(中文版)。如果您认可该宣言,可以在其中签下自己的大名。
    内容不多,这里我们直接拷贝过来。
    在不同领域中深耕的组织都在不约而同地尝试发现相似的软件构建模式。 希望这些系统会更健壮、更具回弹性 、更灵活,也能更好地满足现代化的需求。

    近年来,应用程序的需求已经发生了戏剧性的更改,模式变化也随之而来。仅在几年前, 一个大型应用程序通常拥有数十台服务器、 秒级的响应时间、 数小时的维护时间以及GB级的数据。 而今,应用程序被部署到了形态各异的载体上, 从移动设备到运行着数以千计的多核心处理器的云端集群。 用户期望着毫秒级的响应时间,以及服务100%正常运行(随时可用)。 而数据则以PB计量。 昨日的软件架构已经根本无法满足今天的需求。

    我们相信大家需要一套贯通整个系统的架构设计方案, 而设计中必需要关注的各个角度也已被理清: 我们需要系统具备以下特质:即时响应性(Responsive)、回弹性(Resilient)、弹性(Elastic)以及消息驱动(Message Driven)。 对于这样的系统,我们称之为反应式系统(Reactive System)。

    使用反应式方式构建的反应式系统会更加灵活、松耦合、可伸缩。 这使得它们的开发和调整更加容易。 它们对系统的失败 也更加的包容, 而当失败确实发生时, 它们的应对方案会是得体处理而非混乱无序。 反应式系统具有高度的即时响应性, 为用户提供了高效的互动反馈。

    反应式系统的特质

    • 即时响应性 :只要有可能, 系统就会及时地做出响应。 即时响应是可用性和实用性的基石, 而更加重要的是,即时响应意味着可以快速地检测到问题并且有效地对其进行处理。 即时响应的系统专注于提供快速而一致的响应时间, 确立可靠的反馈上限, 以提供一致的服务质量。 这种一致的行为转而将简化错误处理、 建立最终用户的信任并促使用户与系统作进一步的互动。

    • 回弹性:系统在出现失败时依然保持即时响应性。 这不仅适用于高可用的、 任务关键型系统——任何不具备回弹性的系统都将会在发生失败之后丢失即时响应性。 回弹性是通过复制、 遏制、 隔离以及委托来实现的。 失败的扩散被遏制在了每个组件内部, 与其他组件相互隔离, 从而确保系统某部分的失败不会危及整个系统,并能独立恢复。 每个组件的恢复都被委托给了另一个(外部的)组件, 此外,在必要时可以通过复制来保证高可用性。 (因此)组件的客户端不再承担组件失败的处理。

    • 弹性: 系统在不断变化的工作负载之下依然保持即时响应性。 反应式系统可以对输入(负载)的速率变化做出反应,比如通过增加或者减少被分配用于服务这些输入(负载)的资源。 这意味着设计上并没有争用点和中央瓶颈, 得以进行组件的分片或者复制, 并在它们之间分布输入(负载)。 通过提供相关的实时性能指标, 反应式系统能支持预测式以及反应式的伸缩算法。 这些系统可以在常规的硬件以及软件平台上实现成本高效的弹性。

    • 消息驱动:反应式系统依赖异步的消息传递,从而确保了松耦合、隔离、位置透明的组件之间有着明确边界。 这一边界还提供了将失败作为消息委托出去的手段。 使用显式的消息传递,可以通过在系统中塑造并监视消息流队列, 并在必要时应用回压, 从而实现负载管理、 弹性以及流量控制。 使用位置透明的消息传递作为通信的手段, 使得跨集群或者在单个主机中使用相同的结构成分和语义来管理失败成为了可能。 非阻塞的通信使得接收者可以只在活动时才消耗资源, 从而减少系统开销。
      在这里插入图片描述
      大型系统由多个较小型的系统所构成, 因此整体效用取决于它们的构成部分的反应式属性。 这意味着, 反应式系统应用着一些设计原则,使这些属性能在所有级别的规模上生效,而且可组合。世界上各类最大型的系统所依赖的架构都基于这些属性,而且每天都在服务于数十亿人的需求。现在,是时候在系统设计一开始就有意识地应用这些设计原则了, 而不是每次都去重新发现它们。

    reactive-streams

    官网参见:Reactive Streams
    这里只列举出一些核心的点,完整详细内容请参见官网。

    Reactive Streams是一种非阻塞、响应式、异步流处理、支持背压的标准。包括针对运行时环境(JVM和JavaScript)以及网络协议。

    JDK9 中提供了响应式编程的规范: java.util.concurrent.Flow。一些组件会采用响应式规范来编写,比如java11中提供的支持http/2的HTTP/2 Client API,他不仅可以像Apache HTTPClient、OKhttp一样好用,而且是 JDK 在 Reactive-Stream 方面的第一个生产实践,广泛使用了 Java Flow API。具体请看java11中的java.net.http包。

    Reactive-Stream提供的是一个编程规范,致力于提供非阻塞、响应式、异步流处理、支持背压的策略。不同组织或者语音都可以基于这个规范编写自己的实现。比如现在流行的RxJava、Reactor、AKKA等。以上不同实现都是基于JVM的,都实现了标准的响应式编程规范,原则上,他们之间可以互操作,可以共享内存堆在JVM中的对象,在线程之间传递流。

    Reactive Streams 1.0.2 文档

    展开全文
  • 响应式编程

    千次阅读 2019-05-07 15:26:39
    我们先来聊一聊响应式的由来,对于它的由来,我们可能需要先从一段常见的代码片段看起 int a=1; int b=a+1; System.out.print(“b=”+b) // b=2 a=10; System.out.print(“b=”+b) // b=2 上面是一段很...

    维护关系,却不执行,然后根据触发后再去执行。 相当于原编程运行思路的逆向。

     

    响应式的由来

    我们先来聊一聊响应式的由来,对于它的由来,我们可能需要先从一段常见的代码片段看起

    int a=1;
    int b=a+1;
    System.out.print(“b=”+b)    //  b=2
    a=10;
    System.out.print(“b=”+b)    //  b=2
    
    

    上面是一段很常见的代码,简单的赋值打印语句,但是这种代码有一个缺陷,那就是如果我们想表达的并不是一个赋值动作,而是b和a之间的关系,即无论a如何变化,b永远比a大1。那么可以想见,我们就需要花额外的精力去构建和维护一个b和a的关系。

    而响应式编程的想法正是企图用某种操作符帮助你构建这种关系。
    它的思想完全可以用下面的代码片段来表达:

    
     
    int a=1;
    int b <= a+1;   // <= 符号只是表示a和b之间关系的操作符
    System.out.print(“b=”+b)    //  b=2
    a=10;
    System.out.print(“b=”+b)    //  b=11
    
    

    这就是是响应式的思想,它希望有某种方式能够构建关系,而不是执行某种赋值命令。

    至此你可能不禁要问,我们为什么需要构建关系的代码而不是命令式的代码呢?如果你翻一翻自己正在开发的APP,你就能看到的每一个交互的页面其实内部都包含了一系列的业务逻辑。而产品的每个需求,其实也对应了一系列的业务逻辑相互作用。总之,我们的开发就是在构建一系列的业务逻辑之间的关系。你说我们是不是需要构建关系的代码?

    说回响应式,前期由于真实的编程环境中并没有构建关系的操作符,主流的编程语言并不支持这种构建关系的方式,所以一开始响应式主要停留在想的层面,直到出现了Rx和一些其他支持这种思想的框架,才真正把响应式编程引入到了实际的代码开发中。

    Rx是响应式拓展,即支持响应式编程的一种拓展,为响应式在不同语言中的实现提供指导思想

    什么是响应式编程

    说完了了响应式的由来,我们就可以谈谈什么是响应式编程了。

    响应式编程是一种通过异步和数据流来构建事务关系的编程模型。这里每个词都很重要,“事务的关系”是响应式编程的核心理念,“数据流”和“异步”是实现这个核心理念的关键。为了帮助大家理解这个概念,我们不妨以APP初始化业务为例来拆解一下这几个词。

     

    这是一个比较理想化的APP初始化逻辑,完成SDK初始化,数据库初始化,登陆,之后跳转主界面

    事务的关系

    • 事务

      • 是一个十分宽泛的概念,它可以是一个变量,一个对象,一段代码,一段业务逻辑.....但实际上我们往往把事务理解成一段业务逻辑(下文你均可以将事务替换为业务逻辑来理解),比如上图中,事务就是指APP初始化中的四个业务逻辑。
    • 事务的关系

      • 这种关系不是类的依赖关系,而是业务之间实际的关系。比如APP初始化中,SDK初始化,数据库初始化,登陆接口,他们共同被跳转页面业务所依赖。但是他们三个本身并没有关联。这也只是业务之间较为简单的关系,实际上,根据我们的需求App端会产生出许多业务之间错综复杂的关系。

    数据流

    关于Rx的数据流有很多说法,比如“Everything is a stream”,“Thinking with stream”等等。虽然我明白这只是想强调流的重要性,可是这些话折射出来的编程思路其实是很虚无缥缈的,只会让开发者对于Rx编程更加迷惑。

    实际上,数据流只是事务之间沟通的桥梁。

    比如在APP初始化中,SDK初始化,数据库初始化,登陆接口这些业务完成之后才会去安排页面跳转的操作,那么这些上游的业务在自己工作完成之后,就需要通知下游,通知下游的方式有很多种,其中最棒的的方式就是通过数据(事件)流。每一个业务完成后,都会有一条数据(一个事件)流向下游,下游的业务收到这条数据(这个事件),才会开始自己的工作。

    但是,只有数据流是不能完全正确的构建出事务之间的关系的。我们依然需要异步编程。

    异步

    异步编程本身是有很多优点的,比如挖掘多核心CPU的能力,提高效率,降低延迟和阻塞等等。但实际上,异步编程也给我们构建事务的关系提供了帮助。

    在APP初始化中,我们能发现SDK初始化,数据库初始化,登陆接口这三个业务本身相互独立,应当在不同的线程环境中执行,以保证他们不会相互阻塞。而假如没有异步编程,我们可能只能在一个线程中顺序调用这三个相对耗时较多的业务,最终再去做页面跳转,这样做不仅没有忠实反映业务本来的关系,而且会让你的程序“反应”更慢

    小结

    总的来说,异步和数据流都是为了正确的构建事务的关系而存在的。只不过,异步是为了区分出无关的事务,而数据流(事件流)是为了联系起有关的事务

    APP初始化应该怎么写

    许多使用Rx编程的同学可能会使用这种方式来完成APP的初始化。

    Observable.just(context)
                .map((context)->{login(getUserId(context))})
                .map((context)->{initSDK(context)})
                .map((context)->{initDatabase(context)})
                .subscribeOn(Schedulers.newThread())
                .subscribe((context)->{startActivity()})
    

    其实,这种写法并不是响应式的,本质上还是创建一个子线程,然后顺序调用代码最后跳转页面。这种代码依然没有忠实反映业务之间的关系。

    在我心目中,响应式的代码应该是这样的

    
    Observable obserInitSDK=Observable.create((context)->{initSDK(context)}).subscribeOn(Schedulers.newThread())
    
    Observable obserInitDB=Observable.create((context)->{initDatabase(context)}).subscribeOn(Schedulers.newThread())
    
    Observable obserLogin=Observable.create((context)->{login(getUserId(context))})
                                  .map((isLogin)->{returnContext()})
                                .subscribeOn(Schedulers.newThread())
                                
    Observable observable = Observable.merge(obserInitSDK,obserInitDB,obserLogin)
    
    observable.subscribe(()->{startActivity()})
    
    

    大家应该能很明显看到两段代码的区别,第二段代码完全遵照了业务之间客观存在的关系,可以说代码和业务关系是完全对应的。

    那么这带来了什么好处呢?当initSDK,initDB,Login都是耗时较长的操作时,遵照业务关系编写响应式代码可以极大的提高程序的执行效率,降低阻塞。

    理论上讲,遵照业务关系运行的代码在执行效率上是最优的。

    为什么引入响应式编程

    对响应式编程有了一些了解之后,我知道马上会由很多人跳出来说,不使用这些响应式编程我们还不是一样开发APP?

    在这里我希望你理解一点,当我们用老办法开发APP的时候,其实做了很多妥协,比如上面的APP初始化业务,三个无关耗时操作为了方便,我们往往就放在一个线程环境中去执行,从而牺牲了程序运行的效率。而且实际开发中,这种类似的业务逻辑还有很多,甚至更加复杂。假如不引入响应式的思路,不使用Rx的编程模型,我们面对这么些复杂的业务关系真的会很糟心。假如你做一些妥协,那就会牺牲程序的效率,假如你千辛万苦构建出业务关系,最终写出来的代码也一定很复杂难以维护。所以,响应式编程其实是一种更友好更高效的开发方式。

    根据个人经验来看,响应式编程至少有如下好处:

    • 在业务层面实现代码逻辑分离,方便后期维护和拓展
    • 极大提高程序响应速度,充分发掘CPU的能力
    • 帮助开发者提高代码的抽象能力和充分理解业务逻辑
    • Rx丰富的操作符会帮助我们极大的简化代码逻辑

    一个复杂一些的例子

    接下来,我就以我们团队目前的一款产品的页面为例,详细点介绍运用响应式编程的正确姿势。

    接下来,我就以我们团队目前的一款产品的页面为例,详细点介绍运用响应式编程的正确姿势。

    首先,UI和产品沟通后,可能会给我们这样的设计图(加上一些尺寸的标注)。但是我们并不需要急忙编码,我们首先要做的是区分其中相对独立的模块。

    上图我做了一点简单的标注。把这个页面的业务逻辑简单的分为四个相互独立的模块,分别是视频模块,在线人数模块,礼物模块,消息模块。他们相互独立,互不影响。接下来,我们再去分析每个模块内部的业务并构建起业务之间的关系。大致如下:

    构建了业务之间的关系图,其实我们的工作已经完成了一半了,接下来就是用代码实现这个关系图。在这里,我就以其中一小段业务关系来编写代码给大家示范。

        Observable obserDownload=Observable.just(url)
                                            .map((url)->{getZipFileFromRemote(url)});
        Observable obserLocal=Observable.just(url)
                                            .map((url)->{getZipFileFromLocal(url)});
        Observable obserGift=Observable.concat(obserLocal,obserDownload)
                                            .takeUnitl((file)->{file!=null});
        obserGift.subscribeOn(Schedulers.io()).flatMap((file)->{readBitmapsFromZipFile(file)})
                                            .subscribe((bitmap)->{showBitmap(bitmap)})
    
    
    

    以上是我手写的伪代码,可能细节上有些问题,但大体思路就是这样。

    有人可能会说,那是因为你运用操作符比较熟练才能这么写。其实操作符都是我查的,我记不住那么多操作符,所以基本上我都是先理清楚业务之间的关系,需要和并逻辑的时候,就去去查合并类的操作符,需要条件判断来分流的逻辑时去找条件判断类的操作符。基本上都能满足需求。你瞧,写代码就是这么简单,后续即使需要增加需求,代码修改起来也很清晰,因为无关的业务已经被你分离好了。



    转载自https://www.jianshu.com/p/c95e29854cb1

     

    展开全文
  • 关于响应式编程

    2016-07-25 14:31:38
    近来响应式编程成为一种流行的模式,涌现出很多支持各种...为什么人们会对于响应式编程如此狂热?什么响应式编程?使用它会对于我们的项目哪些帮助?我们应该去学习和使用它吗?同时,Java作为一门支持多线程、高
  • 响应式编程入门

    2018-06-27 18:52:06
    事件、消息 和 方法调用,甚至异常都可以数据流来表达,因此,响应式编程中,我们必须观察这些流,当数据流中数据时,我们对此作出响应。 因此,我们要创建可以拥有任何事物的数据流,或在任何事物(如 点击...
  • 首先,什么是RAC,ReactiveCocoa时Github上的一个Cocoa FRP框架,目的为了接管苹果的所有事件机制(addTarget,代理,通知,KVO,时钟,网络处理);从其强大的作用就可以看出,这是一个超重量级大框架,慎! RAC...
  • Flutter的响应式编程

    2019-09-05 20:23:44
    我们可能都听说过响应式编程这个名词,但什么响应式编程呢?我们先看看维基百科上的定义: 这里面很多的名词,像declarative programming paradigm(声明式编程范式),imperative programming paradigm(指令式...
  • 什么响应式编程? 如果要直接理解,可能比较抽象,但我们可以引用百度百科的例子;例如:a = b + c; 这句代码将b+c的值赋给a,而之后如果b和c的值改变了不会影响到a,然而,对于响应式编程,之后b和c的值的改变也...
  • ios 函数式编程与响应式编程

    千次阅读 2016-08-25 09:45:33
    iOS函数式编程 && 响应式编程概念 函数式编程总结如果想再去调用别的方法,那么就需要返回一个对象; 如果想()去执行,那么需要返回一个block; 如果想让返回的block再调用对象的方法,那么这个block就需要...
  • Spring 5 响应式编程

    千次阅读 2018-01-17 11:22:01
    近年来,响应式编程在开发者社区和客户中很受欢迎,由于其以声明的方式构建应用程序的能力,而不是强制,形成更加敏感和弹性的应用。Spring 5 将反应系统纳入其核心框架的事实已经显示出向声明式编程的范式转变。 ...
  • 一文搞懂: 响应式编程什么?

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

    千次阅读 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, ...
  • 响应式编程在Android中的应用

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

    万次阅读 多人点赞 2015-06-15 17:35:02
    响应式编程简介(原文) 你应该对响应式编程这个新事件有点好奇吧,尤其是与之相关的部分框架:Rx、Bacon.js、RAC等等。 在缺乏好的资源的情况下,学习响应式编程成为痛苦。我开始学的时候,做死地找各种教程。结果...
  • 响应式编程概念,如何一步步实现函数式编程的过程,对阅读Masonry &amp;&amp; SnapKit源码一定的帮助。作为一个iOS 开发者,那么你一定过Masnory/ SnapKit; Masonry是一个OC开发中,非常好用的自动...
  • 响应式编程 Reactive Programming 响应式编程是一种异步编程范式,涉及数据流和变化的传播。 这意味着可以通过所使用的编程语言轻松地表示静态(例如数组)或动态(例如事件发射器)数据流。 作为向响应式编程方向迈出的...
  • 响应式编程简介

    千次阅读 2017-02-17 10:58:53
     你应该对响应式编程这个新事件有点好奇吧,尤其是与之相关的部分框架:Rx、Bacon.js、RAC等等。  在缺乏好的资源的情况下,学习响应式编程成为痛苦。我开始学的时候,做死地找各种教程。结果发现有用的只是...
  • Vue响应式编程

    千次阅读 2019-09-11 18:49:18
    上面这个例子展示的是Vue的响应式能力,的是数值,也可以对 v-bind 的属性进行操作: <!DOCTYPE html> < html lang = " en " > < head > < meta charset = " UTF-8 " > < meta name = " ...
  • 前情提要:响应式编程 | 响应式流 1.5 响应式系统 1.5.1 响应式宣言 关注“响应式”的朋友不难搜索到关于“响应式宣言”的介绍,先上图: 这张图凝聚了许多大神的智慧和经验,见官网,中文版官网,如果...
  • webflux-响应式编程

    2019-03-01 17:31:32
    Java 9 新引入了Reactive Stream(响应式流),而应用了响应式流的编程即为响应式编程 Reactive Stream标准:异步的流处理,并支持非阻塞式的 backpressure(背压? 很拗口的翻译,就是生产者与消费者之者应有流量...
  • iOS函数式编程的实现 & 响应式编程

    千次阅读 2016-04-17 23:25:39
    本篇主要回顾一下--iOS函数式编程 && 响应式编程概念 ,如何一步步实现函数式编程的过程,对阅读Masonry && SnapKit源码一定的帮助。 配图 作为一个iOS 开发者,那么你一定过Masnory/ SnapKit; ...
  • 响应式编程中的Flux和Mono

    万次阅读 多人点赞 2019-04-22 21:46:43
    响应式编程介绍 反应式编程来源于数据流和变化的传播,意味着由底层的执行模型负责通过数据流来自动传播变化。比如求值一个简单的表达式 c=a+b,当 a 或者 b 的值发生变化时,传统的编程范式需要对 a+b 进行重新计算...
  • Kotlin写响应式编程RxAndroid

    千次阅读 2016-03-14 17:25:20
    Kotlin是一门实用的编程语言,可用于JVM和Android程序开发,Kotlin结合了面向对象和函数特性,致力于互操作性、安全、简洁和工具支持。 Kotlin是一门通用的语言,只要能Java的地方就能Kotlin,包含:服务器...
  • 【Scala】响应式编程思想

    千次阅读 2016-05-15 19:47:31
    何为响应式编程响应式编程是一种面向数据流和变化传播的编程范式,数据更新是相关联的。 这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。 以响应...
  • 浅谈Spring5 响应式编程

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

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

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 138,324
精华内容 55,329
关键字:

响应式编程有什么用