响应式编程范式是怎么来的?

状态变化需要响应,这种响应有时候是连锁反应。具体来看一个传统的 HTML 表单界面,相信上网时间长一点的应该都很熟悉了:


古老的网页登录界面

这个简单的表单,要做得好用了,变化响应情况并不简单:
1. 没有输入之前,两个按钮都应该灰掉。
2. 任何一个输入框有输入后,Reset 按钮都被激活。
3. 两个输入框都有输入内容之后,Submit 按钮被激活。
4. 任何一个输入框变空时,Submit 按钮要灰掉。
5. Reset 被点击,清除两个输入框的内容,连锁[1]。
6. Submit 被点击,对每个输入进行有效性检查
a. 全部有效,保存,并前往下一个界面。
b. 任何一个输入无效,将相应的输入框变成红色背景,并在其右侧显示错误信息。
其实还可做得更好,比如在输入时,立即进行有效性检查,弹出自动完成提示等等。

当然,要先知道有变化了,才谈得上响应。通常我们可以主动查询状态(轮询),也可注册到变化源上等通知(回调),还可以用一个中间组件专门处理注册与通知派发(事件分发)。

回调用得比较广泛,写起也直接,但是在有连锁时逻辑会很零散,有依赖关系时也需要主动查询,示意如下:

firstName.onChange : 
    if lastName not empty, submit.enable() 
    else if self is empty, submit.disable();
lastName.onChange :  
    if firstName not empty, submit.enable() 
    else
        if self is empty, submit.disable();
reset.onClick : 
    firstName.clear();
    lastName.clear(); 

这种零散,在程序略微有点长之后,就是一个很大的心智负担,修改规则时也非常容易错漏。考虑到变化响应逻辑实际上有两部分:业务及状态修改,把新状态反映到界面上;有一种改进方法,是后面部分集中起来。比如:

firstName.onChange : updateUI();
lastName.onChange :  updateUI();
reset.onClick : resetFields(); updateUI();

resetFields:
    firstName.clear();
    lastName.clear();
updateUI :
    if lastName is empty, 
        if firstName is empty, submit.disable()
    else 
        submit.enable()

轮循比较难安排响应的时间点,没变化时也要转有点浪费,比如:
timer.on(500ms, updateUI);
不过在大量频繁变化的情况下,可以省掉派遣、回调的开销,保持稳定的性能。

如果有事件派发组件的话,程序可能是这样的:

on textfield-change (src) : updateUI();
on button-click(src) : 
    if ( src is submit ) submit(); 
    if ( src is reset ) resetFields();
    updateUI();

不过,总是逃不掉写那个 updateUI(),如果界面复杂了,还是一样烦人的。其实, 真正有书写价值的只有两条:

submit.enabled = (firstName not empty) && (lastName not empty);
reset.click => resetFields();

如果给事件派发组件再加点功能,其实可以省掉自己去处理 onChange 的:

dispatcher.connect( src: [firstName.text, lastName.text], 
                 target: rest.enabled, 
                   expr:{...} );
dispatcher.on( event: click, src: reset, do: resetFields );

connect()一种可能的实现办法是:当 TextField 在编辑时,它内部会不停的修改 .text 属性,而 dispatcher 把自己注册成 firstName 和 lastName 两个对象的 text 属性的观察者,此时它被触发,执行 expr 并将结果值赋给 target 属性。

对于这种实现方法做一点拔高,我们可以认为:
1. 编写变化响应代码更聚焦在属性和规则上了,也就变得直接。
2. 框架提供的机制替换每次要写的胶水代码,省事儿了。
3. 概念上,属性变化要可被观测。

能告知自己有变化的元素,取个名字叫 Observable;要收到变化通知进行处理的,取个名字叫 Observer。观察者会自动在观测对象变化时被触发,这就是响应式(Reactive)编程了

考虑更复杂的情况,

一个Houdini SOP场景

(from https://sites.google.com/site/fujitarium/Houdini/sop/vdb)
这是 sidefx Houdini 的一个场景,通过右侧的网络定义出左侧图形效果,网络上的一个节点称为一个 OP,节点上下的凸块是输入、输出属性,连接线表示输出起点OP的属性值给终点属性。如果调整一下上图中比较靠上的节点,下游的节点都会受到影响,最终左侧的结果也会立即改变。

对应这个场景的代码,如果能像下面这么写:

(platonic1.out -> convert1.in);
(convert1.out |-> vdbfrompolygons1.in1
              |-> scatter1.in);
(vdbfrompolygons1.out -> vdbfracturel.in1
(sphere1.out -> vdbfracturel.in2);
(scatter1.out -> vdbfracturel.in3);
(vdbfracturel.out -> convertvdb1.in);

然后,platonic1 的运算结果如果有变化,就会自动逐级传下去,最生成正确的结果,是不是很爽?

这种变化影响传播的链条,显然可以看作一种流(Stream)。而函数式编程范式里,将函数做为数据进行流动,是其实程序逻辑的基础。从1980年代的 SISAL 语言开始,函数式语言研究者并已经把这个模式下实现全部程序逻辑需要的流操作,很好的抽象整理了一套出来:map/filter/reduce。Reactive 的响应式,加上 Functional 的流操作,这就是 Functional Reactive Programming (FRP) 啦。

FRP 的精髓就在于,框架替开发者写胶水代码,将变化响应、传递表达得更直接。所以,框架一般还得带一个执行调度器,不然为了把多个异步活动串进响应流里,还得写异步任务调度的胶水代码。这就是现在最热闹的 Rx 系列啦。

看上去 RxAndroid 有点重,Agera 就轻量多了。一直不喜欢 ReactiveCocoa/RxSwift 的实现,RxJS 还不错。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容