除了MVC,P,VM,哥哥教你MVI到底是什么?
,MVI 是在JavaScript中提出的,是单向流,不可变的,响应式的,接收用户输入,通过函数转换为特定的model,并将其结果反馈给用户,我们把这种MVI模式抽象为model(),view(),intent(),三个放方法。这里我解释一下什么是单向流:从view -->intent --> model --> view 。
intent(),接收用户输入的事件将其转换为特定的参数,传给model方法,这个参数可以是一个简单的字符串或者一个复杂的数据结构。
model(),我们可以把一个model理解为一个state,一个model对应一个state,这个model是不可变的,它可以把intent()方法输出作为输入来创建model。要注意的是,我们只能通过model()方法来创建新的model,确保model不变性。
view(),view()方法把model()方法输出的model作为输入,根据model的结果来展示界面。
具体操作如下:
与MVP类似,我们用接口来定义VIew层:
如何把Model展示到界面上呢?我们在View层提供一个render(model)方法,同时View层还要对用户事件做出反应,这就是前面所说的意图(Intent)。再看汇总页面,这里我们只定义2种意图:
意图,是我们以intent作为后缀,每个Intent都是一个流,每个intent都返回observable。
那么接下来怎么把view层的intent和业务关联起来,这里引入Presenter。
其中MviBasePresenter为mosby里的类。通过MviBasePresenter#intent()获取View层的“Intent”,使用Rxjava操作符(map(), flatMap()等)处理业务逻辑最终输出Model,通过MviBasePresenter#subscribeViewState()方法来把这个“流”串起来。
在mosby中MviBasePresenter会在Activity#onStart()时,调用MviBasePresenter#attachView(view),把View和Presenter关联起来,然后执行MviBasePresenter#bindIntent()方法。
MviBasePresenter#intent()方法创建一个PublishSubject对象作为“中继”,在内部维护这个PublishSubject订阅/取消订阅,当屏幕旋转,退到后台等操作,把View与Presenter分离,但此时只会把PublishSubject对象取消订阅,当View “reattach”时(触发Activity#onStart()),对PublishSubject重新订阅。
同时,内部还创建一个BehaviorSubject对象作为业务逻辑和View层的“中继”,当调用MviBasePresenter#subscribeViewState()方法,如上,让allIntents订阅这个BehaviorSubject对象,再对BehaviorSubject对象内部进行订阅,调用SummaryView#render。当订阅BehaviorSubject对象,会发射最后一个值,即当View “reattach”时,会发射最后的Model,那么我们就可以默认展示上一次的界面。
在实际项目中,业务逻辑一般都会比较复杂,这样会导致Presenter越来越臃肿,可读性,可维护性,可测试性价低,我们应该分离并提供创建Model的方法,如提供“Interactor”。
State Reducer
State Reducer是函数式编程的概念,以上一个状态作为输入并输出新的状态。代码描述如下:
我们用Foo来表示当前相对于上一次状态的变化(如loading,加载下一页),通过reduce()方法,结合上一次的状态preState的值创建一个新的状态并返回。这样在加载下一页数据时就可以获取到上一页的数据
我们如何实现这个"reduce"呢?我们创建Model时先返回当前的变化(MainPartialStateChanges),然后通过viewStateReducer方法输出最终的状态(MainViewState), 我们使用Rxjava的操作符scan(),调用viewStateReducer()方法输出最终的MainViewState。在viewStateReducer()方法中通过MainPartialStateChanges对象的值和上一次的MainViewState来生成新的MainViewState。
注:上面的代码通过类型判断来区分不同的状态,这样实现并不优雅,当状态特别多的时候viewStateReducer()会十分庞大,这里只是作为demo事例,让大家跟好理解。在实际项目中最好使用设计模式,一个比较简单的做法,我们可以在MainPartialStateChanges中定义一个reduce(MainViewState)方法,其中参数就是上一次的状态:
Side Effect(副作用)
BehaviorSubject在订阅的时候会发射最后一个值,即Model,而订阅的时机是Activity#onStart()。我们考虑下这样的场景:我们把数据保存到服务端,保存成功之后主动展示一个新的页面(NewActivity),使用MVI的做法就是在view.render()里直接跳转,这样做的话你会发现从NewActivity返回的时候NewActivity会被重新打开,因为Activity#onStart()被触发(大家脑海里回顾下Activity的生命周期)。Hannes Dorfmann大神认为页面跳转不是一种状态(不是对界面的描述),他给出的一个解决方案是添加一个Navigator类来进行页面跳转。具体请看这个issues Navigation in MVI。
在MVI中Presenter的概念并不强,我们仅仅是让它连接业务逻辑和View层,上面也提到过Interactor和reduce。MVI只是一种思想,可以有不同实现方式,如TODO-MVI-RxJava,deck-of-cards ,这2个项目把MVI分层分得更细。