本文为稀土技能社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

复杂度

Android 架构演进系列是围绕着复杂度向前推动的。

软件的首要技能使命是“办理复杂度” —— 《代码大全》

由于低复杂度才干降低了解成本和交流难度,进步应对改变的灵活性,减少重复劳动,终究进步代码质量。

架构的目的在于“将复杂度分层”

复杂度为什么要被分层?

若不分层,复杂度会在同一层次打开,这样就太 … 复杂了。

举一个复杂度不分层的例子:

小李:“你会做什么菜?”

小明:“我会做用土鸡生的土鸡蛋配上切片的西红柿,放点油盐,开战翻炒的西红柿炒蛋。”

听了小明的回答,你还会和他做朋友吗?

小明把不同层次的复杂度以不恰当的办法搓弄在一起,让人感觉是一种由“没有必要的具体”导致的“难以了解的复杂”。

小李其实并不关怀土鸡蛋的来源、西红柿的切法、增加的佐料、以及烹饪办法。

这样的回答除了难以了解之外,局限性也很大。由于它太具体了!只要把土鸡蛋换成洋鸡蛋、或是西红柿片换成块、或是加点糖、或是换成电磁炉,其间任一因素发生改变,小明就不会做西红柿炒蛋了。

再举个正面的例子,TCP/IP 协议分层模型自下到上定义了五层:

  1. 物理层
  2. 数据链路成
  3. 网络层
  4. 传输层
  5. 使用层

其间每一层的功用都独立且明确,这样规划的优点是缩小影响面,即单层的改变不会影响其他层。

这样规划的另一个优点是当专注于一层协议时,其余层的技能细节能够不予重视,同一时间只需求重视有限的复杂度,比方传输层不需求知道自己传输的是 HTTP 仍是 FTP,传输层只需求专注于端到端的传输办法,是树立衔接,仍是无衔接。

有限复杂度的另一面是“基层的可重用性”。当使用层的协议从 HTTP 换成 FTP 时,其基层的内容不需求做任何更改。

引子

该系列的前三篇结合“查找”这个事务场景,叙述了不运用架构写事务代码会发生的痛点:

  1. 低内聚高耦合的制作:控件的制作逻辑散落在遍地,散落在各种 Activity 的子程序中(子程序间彼此耦合),涣散在现在和将来的逻辑中。这样的规划增加了界面改写的复杂度,导致代码难以了解、简略改出 Bug、难排查问题、无法复用。
  2. 耦合的非粘性通讯:Activity 和 Fragment 经过获取对方引证并互调办法的办法完成通讯。这种通讯办法使得 Fragment 和 Activity 耦合,然后降低了界面的复用度。而且没有一种内建的机制来轻松的完成粘性通讯。
  3. 天主类:一切细节都在界面被铺开。比方数据存取,网络访问这些和界面无关的细节都在 Activity 被铺开。导致 Activity 代码不单纯、高耦合、代码量大、复杂度高、改变源不单一、改动影响规模大。
  4. 界面 & 事务:界面展现和事务逻辑耦合在一起。“界面该长什么样?”和“哪些事情会触发界面重绘?”这两个独立的改变源没有做到重视点别离。导致 Activity 代码不单纯、高耦合、代码量大、复杂度高、改变源不单一、改动影响规模大、易改出 Bug、界面和事务无法独自被复用。

具体分析进程能够点击下面的链接:

  1. 写事务不必架构会怎么样?(一)
  2. 写事务不必架构会怎么样?(二)
  3. 写事务不必架构会怎么样?(三)

紧接着又用了三篇叙述了怎么运用 MVP 架构对该事务场景的重构进程。MVP 确实处理了一些问题,但也引进了新问题:

  1. 分层:MVP 最大的贡献在于将界面制作与事务逻辑分层,前者是 MVP 中的 V(View),后者是 MVP 中的 P(Presenter)。分层完成了事务逻辑和界面制作的解耦,让各自愈加单纯,降低了代码复杂度。
  2. 面向接口通讯:MVP 将事务和界面分层之后,各层之间就需求通讯。通讯经过接口完成,接口把做什么和怎么做别离,使得重视点别离成为或许:接口的持有者只关怀做什么,而怎么做留给接口的完成者关怀。界面经过事务接口向 Presenter 宣布请求以触发事务逻辑,这使得它不需求关怀事务逻辑的完成细节。Presenter 经过 view 层接口回来呼应以辅导界面改写,这使得它不需求关怀界面制作的细节。
  3. 有限的解耦:由于 View 层接口的存在,迫使 Presenter 得了解该把哪个数据塞给哪个 View 层接口。这是一种耦合,Presenter 和这个具体的 View 层接口耦合,较难复用于其他事务。
  4. 有限内聚的界面制作:MVP 并未向界面供给仅有 Model,而是将描绘一个完整界面的 Model 涣散在若干 View 层接口回调中。这使得界面的制作无法内聚到一点,增加了界面制作逻辑维护的复杂度。
  5. 困难重重的复用:理论上,界面和事务分层之后,各自都愈加单纯,为复用供给了或许性。但不管是事务接口的复用,仍是View层接口的复用都相当别扭。
  6. Presenter 与界面共存亡:这个特性使得 MVP 无法应对横竖屏切换的场景。
  7. 无内建跨界面(粘性)通讯机制:MVP 无法高雅地完成跨界面通讯,也未内建粘性通讯机制,得凭借第三方库完成。
  8. 生命周期不友好:MVP 并未内建生命周期办理机制,易形成内存泄漏、crash、资源糟蹋。

具体分析进程能够点击下面的链接:

  1. MVP 架构终究审判 —— MVP 处理了哪些痛点,又引进了哪些坑?(一)
  2. MVP 架构终究审判 —— MVP 处理了哪些痛点,又引进了哪些坑?(二)
  3. MVP 架构终究审判 —— MVP 处理了哪些痛点,又引进了哪些坑?(三)

再然后用了两篇叙述了 MVVM 架构是怎么处理 MVP 的痛点:

  1. ViewModel 的引进使得“有免死金牌的事务层”成为或许,也使得跨界面之间的事务逻辑共享以及通讯变得轻松。
  2. LiveData 的引进使得事务层成为数据持有者以数据驱动改写界面,还避免了生命周期问题以及内存泄漏风险。
  3. 由于数据持有者,MVVM 也引进了新的复杂度,首要是不优点理的粘性数据问题,更棘手是更新数据的办法是带有副作用的,由此会引发界面状况不一致问题。

关于 MVVM 架构的具体分析能够点击下面的链接:

  1. “无架构”和“MVP”都救不了事务代码,MVVM能力挽狂澜?(一)
  2. “无架构”和“MVP”都救不了事务代码,MVVM能力挽狂澜?(二)

从这一篇开端,试着引进 MVI 架构的思维进行查找事务场景的重构,看看是否能处理上述痛点。

在重构之前,再介绍下查找的事务场景,该功用示目的如下:

业务架构的救世主是 MVI(一)

事务流程如下:在查找条中输入关键词并同步展现联想词,点联想词跳转查找成果页,若无匹配成果则展现引荐流,回来时查找前史以标签形式横向铺开。点击前史可直接建议查找跳转到成果页。

将查找事务场景的界面做了如下规划:

业务架构的救世主是 MVI(一)

查找页用Activity来承载,它被分红两个部分,头部是常驻在 Activity 的查找条。下面的“查找体”用Fragment承载,它或许呈现三种状况 1.查找前史页 2.查找联想页 3.查找成果页。

Fragment 之间的切换选用 Jetpack 的Navigation。关于 Navigation 具体的介绍能够点击Navigation 组件运用入门 | Android 开发者 | Android Developers

MVP & MVVM & MVI 架构图比对

MVI 和 MVP/MVVM 不是非此即彼的联系,它们是不同维度的。

MVP 和 MVVM 关怀的是事务层的形状,MVP 中事务层用 Presenter 表达,如下图所示:

业务架构的救世主是 MVI(一)

而 MVVM 中事务层用 ViewModel 表达,如下图所示:

业务架构的救世主是 MVI(一)

MVI 不关怀事务层形状,而是关怀事务数据改换及活动的形状。

业务架构的救世主是 MVI(一)

将名词解释放在一边,先来看看架构图表达形式上的差异。

MVI 的图和其他两个相比有一个明显的区别,之前运用的是{},比方View{Presenter{}}表明 View 持有一个 Presenter。但 MVI 的架构图中运用的是(),表明一个函数,即 Model 是 Intent 的函数,View 是 Model 的函数。

函数 & 函数式编程

函数即两个调集之间的一种对应联系。若对调集 x 中的值施加规律 f 后都能仅有对应调集 y 中的一个值,则说 y 是 x 的函数,记为y = f(x)

这儿的关键是仅有自变量 x 对应仅有应变量 y。在数学中这是简略的一元函数,在编程中这是一种低复杂度,低复杂度意味着不会犯错。

把一元函数进一步具象化到界面改写这个 case 上,能够表达为 “一个 Model 仅有对应一个界面状况”,记为view = f(model)

按照这个思维回看一下 MVVM 架构中的函数联系(同样的问题也存在于 MVP 中),界面的状况有若干个LiveData<Model>表达,即 viewState = f(model1, model2, model3, …),其间任何一个 model 都能够独立发生改变,而任一 model 改变后,都会引起 viewState 的改变,即一切 model 的任一排列组合与一个 viewState 对应。这个复杂度就很高,犯错的概率就很大,这样的过错称为界面状况不一致,即界面状况和你料想的不一样,由于有一种排列组合没有考虑到。关于实战中界面状况不一致的实例分析能够点击“无架构”和“MVP”都救不了事务代码,MVVM能力挽狂澜?(一)

MVI 把函数对应联系做到了极致,它把界面改写的整个流程都表达成了一元函数。首要界面建议的动作被抽象为数据 Intent,Intent 的函数是 Model,即model = f1(intent),表明任一事务动作会发生仅有对应的 model,紧接着任一 Model 对应仅有界面状况,即view = f2(model)

f1 和 f2 这两个一元函数描绘了事务目的、数据、界面状况之间一一对应的联系,在需求文档确认下来的一起,这一一对应的联系就已固定下来。

将函数的思维使用到编程,就发生了函数式编程

函数式编程是一种编程范式,即关于怎么编写程序的办法论。它的首要思维是把运算进程尽量写成一系列嵌套的函数调用。

这种编程范式最大的优点是没有副作用。副作用是指函数内部与外部互动,发生运算以外的其他成果。最典型的状况是修正全局变量。关于 MVVM 中的副作用详解能够点击“无架构”和“MVP”都救不了事务代码,MVVM能力挽狂澜?(二)。

而函数式编程中只有输入参数和回来值,不修正全局变量。从耦合的角度来看,函数式编程中的函数只包含运算且不与任何东西耦合,这使得它复杂度低、运转成果可猜测、易于单元测试、调试。函数式编程是 MVI 架构相较于其他架构的一个明显不同点。

综上,运用 MVI 架构的开发进程即是:

运用函数式编程思维将需求翻译成事务目的(I)、数据(M)、界面状况(V)间的函数联系,再用呼应式编程的办法将其串联成数据流的进程。

最后将函数串联的办法是呼应式编程。它是一种面向数据流的编程范式。关于呼应式编程的具体介绍能够点击Android 架构之 MVI 雏形 | 呼应式编程 + 单向数据流 + 仅有可信数据源

除了编程范式上之外,MVI 架构还有一些其他的不同点,引证之前文章的总结:

MVI = 呼应式编程 + 单向数据流 + 仅有可信数据源

关于这三条规范的剖析能够点击该系列文章:

  1. Android 架构之 MVI 雏形 | 呼应式编程 + 单向数据流 + 仅有可信数据源
  2. Android 架构之 MVI 初级体 | Flow 替换 LiveData 重构数据链路
  3. Android 架构之 MVI 完全体 | 重新审视 MVVM 之殇,PartialChange & Reducer 来拯救
  4. Android 架构之 MVI 究极体 | 状况和事情分道扬镳,粘性不再是问题

其间的呼应式编程会运用 Kotlin Flow,关于它的具体介绍能够点击:

  1. Kotlin 异步 | Flow 使用场景及原理
  2. Kotlin 异步 | Flow 限流的使用场景及原理

总结

在具体分析 MVI 的完成细节之前,对其做一个概念性总结:

MVI 用数据流来了解界面改写:界面是数据流的起点(生产者)也是终点(顾客),界面宣布的数据叫目的,目的会用函数式编程的办法被改换为状况,终究状况经过呼应式编程的办法流向界面,界面消费状况完成改写。在这个活动的进程中,若保证了仅有可信数据源,就能完成单向数据流。

下一篇会根据查找这个事务场景,具体打开 MVI 的完成细节。