从旧博客上转移文章:
一篇不错的理解响应式编程的入门文章。个人把它分成2部分,慢慢翻译。
目录:
- 什么是响应式编程(1)
- 使用响应式编程思考的例子(2)
你不能错过的响应式编程(Reactive Programming)介绍
你可能对学习响应式编程这种新兴事物感兴趣,尤其当它的变种包括了Rx,Bacon.js,RAC等听说过的库。
学习它非常困难,尤其当没有好的入门材料的时候。当我开始的时候,我曾尝试寻找入门教程。我发现只有屈指可数的实际指导资料,而且大部都分只是抓痒,从没有解决如果围绕响应式编程创建完整架构的挑战。相关类库的文档经常毫无意义当你试图理解某些功能的时候。我是指,诚实的说,像下面这个文档:
Rx.Observable.prototype.flatMapLatest(selector, [thisArg]) Projects each element of an observable sequence into a new sequence of observable sequences by incorporating the element's index and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence.
(Enix:不翻译了,基本文档只描述了显而易见的方法使用说明,而不是为什么这么做。插一句,个人以为,这种文档/注释,完全重复代码本身所能传达的信息的,不要也罢。难道是为了凑注释率?)
我的天啊!
我读过两本关于响应式编程的书,一本只描绘了大局,而另一本分成好多部分讲述了如何运用各种Reactive的类库。我最后只能通过最困难的方式学习响应式编程:边构建项目边学习。
(Einx:边构建项目边学习其实是我个人非常推荐的学习方式,比看了一遍书,觉得自己已经会了强多了)
我在Futurice工作的时候,我有机会把它实际应用在项目里。在同事们的帮助下解决了一些问题。
学习的过程中,最难的部分就是转换你的思想,响应式的思考。你必须放下你已经学会的命令式的、有状态的典型编程思维,强制转换你的大脑的工作模式。我在网上没有找到任何教程来帮助你完成这种转换,我觉得世界需要有这样的一个教程来教大家如何响应式思考。希望这篇文章能帮助大家开始理解,再阅读相关类库的文档就不会这么困难了。
什么是响应式编程?
网上有太多很差的解释和定义。维基百科的定义太抽象和理论化了,Stackoverflow的回答明显不适合刚刚入门的人。Reactive Manifesto的内容看起来是给你们公司的项目经理和业务人员看的。微软的Rx术语定义:“Rx = Observables + LINQ + Schedulers”是如此之微软式的重量级,让大部分人都感到疑惑。一些术语如“响应式”和“变化的传播”并没有传达任何与现有典型技术(如MV*)和语言的明显区别。所有的框架的所有的View都是响应Models的,所有的变化都是被传播的。不然,前端页面就不会被渲染出来了。
所以,废话少说,切入正题。
响应式编程是面对异步数据流的编程方式
换句话说,没有任何新的东西。事务总线(Event bus)或者一个典型的点击事件,其本身就是一个异步的事件流,你能监视这些事件并相应的做一些动作。响应式就是基于此的。你能够创建一个数据流包含任何东西,不只是点击事件和鼠标Hover事件。流(steams)的概念是廉价和普遍的,任何东西都可以变成流的一部分:变量,用户输入,属性,缓存,数据结构。。。举例来说,想象你的Twitter feed(天朝用户请自适应到Weibo),里面的帖子就是以数据流形式存在的,和用户点击事件的数据流等价。你的程序的任务就是监听这个流并作出相应的动作(也就是所谓的响应,react)。
抽象来说,你被给予了一个神奇的工具盒,盒子里包含了各种各样的功能/函数(Functions),你的任务就是组合这些功能,来处理那些流(创建、过滤等等)。这也是为什么函数式编程一般都会参与进来的道理。一个流可以作为其他流的输入,甚至多个流可以一起作为一个其他流的输入。你可以合并两个流,你可以过滤某个流来产生一个新的,只包含你关心的事情的流。你可以把一个流里面的数据值映射到另外一个新的流里面。
既然流这个概念在响应式的地位是如此重要,让我们仔细看看他们。以一个我们最熟悉的“点击按钮”事件的流开始。
(Enix:黄色的圆圈是一个代表点击事件的值,红色的叉叉表示错误,最右的黑竖线表示流结束)
一个流由一些在时间上顺序发生的事件组成。它可以发出三种事件:一个值(某个类型的值),一个错误,一个结束信号。以结束信号为例,它代表了当前按钮所在的窗口或者视图被关闭。
我们仅仅异步的捕捉流所发出的事件,通过定义在值被发出时执行的函数,在错误被发出时执行的函数,在结束信号被发出时执行的函数。而大部分情况下,后两个事件可以暂时忽略,我们可以专注于值被发出时触发的函数。这种监听流的方式被叫做订阅(subscribing)。我们所定义的执行函数被称作观察者。流被称作主题(subject)或者可观察的(observable)。听起来熟悉么,这就是设计模式里面的观察者模式。
上图还有另外一种ASCII的表达形式,在本文的后面都会采取这种形式的图(Enix:方便偷懒)。
--a---b-c---d---X---|->
a, b, c, d are emitted values
X is an error
| is the 'completed' signal
---> is the timeline
既然大家已经很熟悉观察者模式,我也不想变得无聊,我们来做点不一样的:我们要来创建新的点击事件流,从原先的点击事件流里变形而来。
首先,让我们来做一个统计按钮被点击次数的计数流。在一些常见的响应式库里,每个流都暴露了许多相关的方法,比如map, filter,scan等等。当你调用其中的一个函数时,比如clickStream.map(f),函数返回一个新的stream基于当前的clickStream。函数调用并不会改变原始的点击事件流。这称之为流的不可变(immutability),记住这一点当你使用响应式编程里的流,他们之间的关系就像煎饼总是要沾酱吃一样。这个特性也允许我们使用链式函数,比如:clickStream.map(f).scan(g):
clickStream: ---c----c--c----c------c-->
vvvvv map(c becomes 1) vvvv
---1----1--1----1------1-->
vvvvvvvvv scan(+) vvvvvvvvv
counterStream: ---1----2--3----4------5-->
函数map(f)把当前流中的每个发出的值都通过你提供的函数f转换成新流中的值。在上面的例子里,我们把每次点击事件都映射成数值1.函数scan(g)用来聚合所有流中流过的值,产生的值x=g(accumulated,current),其中g就是一个简单的累加函数。把上面的所有合在一起,新的counterStream会在任何点击发生时,发出点击的总数。
为了展示响应式编程的真正威力,让我们假设你需要一个“双击事件”的流。为了让事情变得更加有趣,让我们进一步假设新的流必须把连续三次点击也作为双击事件处理,或者更一般的来说,考虑所有多次点击事件(两次及以上)。现在,深呼吸一次,然后想想在以前,我们用命令式编程,带状态的编程思想时会怎么做。我敢打赌程序会写的很难看,其中包括了好多变量、状态、一些对时间间隔的摆弄。
让我们看看在响应式编程里面会变得多么简单吧。实际上,这个逻辑只需要4行代码。让我们暂时先忘记代码,图仍然是最好的理解和构建流的方式,对初学者和专家来说都一样。
图中灰色的长方形表现了如何把一个流转换成另外一个。首先我们聚合列表里面的点击事件,把间隔小于250毫秒的点击事件聚合到一起:buffer(stream.throttle(250ms))。先不要操心如何理解这里使用到的函数的一些细节,我们的目的只是演示如何响应式。对于聚合好的流,我们再次使用map()来把流中的每一组点击事件,转换成相应的点击次数。最后,我们使用filter(x >= 2)这个函数,把流中值等于1的点击事件都过滤掉。上面的三次转换,最后给了我们期望的流。我们可以订阅(监听)这个流,对事件作出相应的响应。
我希望你能看出这个解决方案的优雅之处。而这个例子只是冰山一角,你可以把相应的操作放到完全不同的流上,比如,一个API响应的流;你也可以使用不同的操作,响应式编程库一般都提供了很多方便的函数。
(Enix:使用流的转换的确更优雅,一下子就能想到好多应用场景,特别是前端的。但那个可疑的聚合函数throttle是由类库实现的,优雅的同时不要忘记是响应式编程类库帮你做了这个恶心的聚合逻辑。)
为什么我要考虑采用响应式编程?
响应式编程提升了你代码的抽象层次,让你可以更关注与组成业务逻辑的事件的相互关系,减少你迷失在具体实现的汪洋大海里的机会。使用响应式编程能使你的设计变得更简洁。
响应式编程的优点在现代软件的环境下更突出了,更多的网站应用、手机应用,更多的用户交互体验(比如和UI事件打交道)。。。 10年以前,一个网页的所有交互基本上都是提交一个又臭又长的表单到后台,然后把结果简单的显示在浏览器里。现在的网页应用已经进化到拥有更丰富的实时功能:只要你在表单里面编辑了一个字段,验证、保存等等功能可能马上在后台触发,类似Facebook里面“赞”的功能,也会实时的反应到所有连接用户的页面上。
今天的应用都有海量的实时事件处理需要,高互动的用户体验已经是软件必不可少的一部分了。我们需要工具来帮助我们更好的处理这些,而响应式编程是一个很好的答案。
(Enix:+1,作者敏锐的指出了响应式编程的理想应用场景)