这几天讨论 MVI 的人有点多,我简单分享下自己的理解。
MVI 是响应式编程的产物。
响应式编程有个潜规则:一个控件只能在同一个粘性观察者( BehaviorSubject )中响应,
然而随着业务发展,开发者很容易误在多个粘性观察者中放置同一控件实例,即引发 “多个粘性观察者不符预期回推” 的隐患,
MVI 主要就是来解决这问题。通过将页面 uiStates 聚合在一处,控件只从这唯一观察者响应数据变化,从而确保环境重建时,回推的最后一次数据来源唯一,乃至符合预期。
由于控件集中响应,uiStates 字段不可变,加上每次根据 intent 执行业务,拿到的都是当前 action 当前 partialChange ,故唯有通过 copy 整合当前 partialChange 到上一次 uiStates ,来确保 uiStates 的延续,这个 copy 以延续的过程就叫 reduce ,
不过如果不是有专门的需求,我们无须特意写个 reducer 类来体现,而仅仅是 reduce 方法或直接 copy ,心意到了即可,
与此同时,UI 侧都是通过 diff 来判断本次某控件是否刷新。jetpack compose 无须开发者手动 diff ,其内部类似前端 DOM ,根据本次注入的声明树自行在内部差分合并渲染新的内容。当前我个人是用 DataBinding observableField 来做 diff 。
当然 MVI 也有其不便之处,由于它本来就是要通过聚合 uiStates 来规避上述不符预期问题,故 uiStates 很容易爆炸,特别是 UiStates 居多情况下,每次回推都要做数十个 diff ,在高实时变化的场景下,有一定的性能影响,
MVI 许多页面和业务都需手写定制,难通过自动生成代码等方式半自动开发,故我们我们不如退一步,反思下为什么要用响应式编程?
穷尽所有可能,我觉得最合理的解释是,响应式编程十分便于单元测试 —— 由于控件只在观察者中响应,那么有输入就必定有回响,
也是因为这原因,官方出于完备性考虑,以响应式编程作为架构示例。
现实中情况往往十分复杂。
android 当初为了站稳脚跟,选择复用已有的 java 生态和开发者,乃至使用 java 来作为官方语言,后来 java 越来越难支持现代化移动开发,故而转向 kotlin ,
用 kotlin 的开发者,更容易跟着官方文档走,一开始就是接受 flow 、reactive 的那一套,且 kotlin 的确抹平了语法复杂度,所以天然就是响应式编程的模式在开发,如此便有机会踩坑,并且有动力通过 MVI 的方式来改善响应式编程开发。
然而 10 个 android 7 个纯 java ,其中 6 个从不用 rxJava ,剩下一个还是偶尔用用 rxjava 的线程调度切换,
所以响应式编程在 java android 开发中的推行不太理想,领导甚至可能为了照顾多数同事,而要求撤回响应式编程代码,如此便很难有机会踩坑,更谈不上使用 MVI ,
也因此,实际开发中更多考虑的是,如何从根源上避免各种不可预期问题。对此从软件工程角度出发,我在设计模式原则中找到答案 —— 任何框架,只要遵循单一职责原则,就能有效避免各种不可预期问题,反之过度设计则易引发不可预期问题,
例如 BehaviorSubject ,实际上是一种过度设计,因为它的观察者是开放式,一旦开了这口子,后续便不可控,一个良好的设计是,不暴露不该暴露的口子,不给用户犯错的机会。一个正面的案例是 DataBinding observableField ,一个控件只能在 xml 中绑定一个,从根源上杜绝上述不符预期问题。
不过 DataBinding 也存在过度设计,例如开发者能拿到 binding 实例去直接调用控件实例,如此 observableField 数据绑定的努力前功尽弃。
目前想到的就是这些,有不同观点欢迎补充。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.