国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟

這篇具有很好參考價值的文章主要介紹了談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報違法"按鈕提交疑問。

作者:leobertlan

前言

當(dāng)時項(xiàng)目采用MVP分層設(shè)計(jì),組員的代碼風(fēng)格差異也較大,代碼中類職責(zé)賦予與封裝風(fēng)格各成一套,隨著業(yè)務(wù)急速膨脹,代碼越發(fā)混亂。試圖用 MVI架構(gòu) + 單向流 形成 掣肘 帶來一致風(fēng)格。 但這種做法不夠以人為本,最終采用 “在MVP的基礎(chǔ)上進(jìn)行了適當(dāng)改造+設(shè)計(jì)約定的方式” 解決了問題,并未將MVI投入到商業(yè)項(xiàng)目中,于是 放棄了紙上談兵。

在半年前終于有機(jī)會在商業(yè)項(xiàng)目中進(jìn)行實(shí)踐,同諸位談一談使用后的 個人感悟 ,并藉此講透MVI等架構(gòu)。

所有內(nèi)容將按照以下要點(diǎn)展開:

  • 從架構(gòu)的理念出發(fā) – 簡單列明各種 MVX 的理念 , MVX:指代 MVC、MVP、MVVM、MVI
  • 擁抱復(fù)雜的同時實(shí)現(xiàn)簡化 – 通過對比理解單向數(shù)據(jù)流動所解決的痛點(diǎn)、設(shè)計(jì)Intent的原因等問題
  • 單一可信數(shù)據(jù)源,不可僵化信奉
  • 要想優(yōu)雅,需要工具 – 借助聲明式、響應(yīng)式編程工具,構(gòu)建屏蔽命令式編程中的細(xì)節(jié),同樣是聚焦和簡化
  • 狀態(tài)和事件分家,絕不是吃飽了撐的 – 為什么要裂變出狀態(tài)和事件,如何界定

內(nèi)容會很長,我會酌情再寫一些 ,結(jié)合實(shí)例和代碼演示內(nèi)容。

兩個項(xiàng)目的基本情況

相比于之前的巨型項(xiàng)目,這兩個項(xiàng)目的業(yè)務(wù)量均不大,一個是基于藍(lán)牙和局域網(wǎng)的操控類APP,下午簡稱APP-A,一個是內(nèi)部使用的工具,分析公司各個產(chǎn)品的日志,簡稱APP-B。

雖然他們的業(yè)務(wù)深度要比一般的APP要深,但在 本質(zhì)上一致 ,畢竟同類型業(yè)務(wù)量再多也僅僅是重復(fù)運(yùn)用一套模式 ,并不影響本質(zhì)。

和諸多項(xiàng)目的本質(zhì)一致,均符合如下圖所示的邏輯分層,并在人機(jī)交互過程中執(zhí)行業(yè)務(wù)邏輯:

談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟,Android,架構(gòu),移動開發(fā),架構(gòu),android,移動開發(fā),軟件框架,MVI

  • APP-A 是Android項(xiàng)目,圖方便純kotlin
  • APP-B 是 Compose-Desktop項(xiàng)目,不得不kotlin

過于絮叨了,我們進(jìn)入正文。

從架構(gòu)的理念出發(fā)

謹(jǐn)記,實(shí)際情況中,MVI、MVVM這些架構(gòu)均先由Web應(yīng)用領(lǐng)域提出,用于解決瀏覽器Web應(yīng)用研發(fā)中的問題。

在后續(xù)的應(yīng)用領(lǐng)域發(fā)展過程中,存在共性問題,便引入了這些設(shè)計(jì),并結(jié)合自身特點(diǎn)進(jìn)行了拓展。

接下來我們聊一聊理念,不比武功。

談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟,Android,架構(gòu),移動開發(fā),架構(gòu),android,移動開發(fā),軟件框架,MVI

圖片出自電影一代宗師

MVI的理念

MVI 脫胎于 Model View Intent

  • Intent:驅(qū)動model發(fā)生改變的意圖,以UI中的事件最為常見;
  • Model:業(yè)務(wù)模型,包含數(shù)據(jù)和邏輯,是對應(yīng) 客觀實(shí)體程序建模;
  • View:表現(xiàn)層的視圖,以UI方式呈現(xiàn)Model的狀態(tài)(以及事件),接受用戶輸入,轉(zhuǎn)換為UI事件

官方的這幅圖很好的呈現(xiàn)了三者之間的驅(qū)動關(guān)系:

談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟,Android,架構(gòu),移動開發(fā),架構(gòu),android,移動開發(fā),軟件框架,MVI

這張圖非常簡單,它摒棄了驅(qū)動方式的細(xì)節(jié),只體現(xiàn)了角色與驅(qū)動關(guān)系。

注意,只要設(shè)計(jì)中滿足 角色和驅(qū)動關(guān)系 符合上圖,就是MVI架構(gòu)設(shè)計(jì),并不限制 驅(qū)動方式的實(shí)現(xiàn)細(xì)節(jié)

經(jīng)典的MVI驅(qū)動細(xì)節(jié)要比上圖復(fù)雜很多,下文再聊。

從軟件設(shè)計(jì)的原則出發(fā):職責(zé)分離并封裝 的目的是 解耦可獨(dú)立變化、復(fù)用。

顯然,區(qū)別于 MVVM 、 MVPMVC,角色上的差別在于 ViewModel、Presenter、Controller、Intent四者,而它們又是View和Model之間的紐帶。除此之外,V和M亦稍有不同。

MVC、MVP

MVC、MVP 中,C和P的職責(zé)體現(xiàn)為 控制、調(diào)度。

MVP中 VM 完全解耦可獨(dú)立變化,MVC中 M 直接操作 V 耦合高,在web應(yīng)用中,C 需要直接操作DOM。

MVVM

MVVM中,提倡 數(shù)據(jù)驅(qū)動, 數(shù)據(jù)源 被剝離到 VM 中,在 雙向綁定框架 的加持下,View層的輸入反映為數(shù)據(jù)的變化,數(shù)據(jù)的變化驅(qū)動視圖內(nèi)容。

顯然,VM的職責(zé)限于維護(hù)數(shù)據(jù)狀態(tài),如有必要,驅(qū)動View層消費(fèi)數(shù)據(jù)狀態(tài), 不必再關(guān)注如何操作視圖。

一般來說,雙向綁定框架已經(jīng)引入觀察者模式實(shí)現(xiàn),可響應(yīng)式驅(qū)動,VM一般沒有必要關(guān)心 響應(yīng)式驅(qū)動和下游觀察者生命周期問題

簡單思考之后會發(fā)現(xiàn)MVVM的問題,它的側(cè)重點(diǎn)在于 利用雙向綁定讓開發(fā)者專注于數(shù)據(jù)狀態(tài)的維護(hù),從操作視圖更新中得以解放,它難以解決 無天然狀態(tài) 問題,例如:按鈕點(diǎn)擊這類事件。

MVI

在MVI中,結(jié)合業(yè)務(wù)背景將UI事件等內(nèi)容轉(zhuǎn)換為 Intent ,驅(qū)動Model層業(yè)務(wù),Model層的業(yè)務(wù)結(jié)果反映為 視圖狀態(tài) + 事件。

因此View層和Model層之間已經(jīng)解耦,并可以吸收MVVM中的優(yōu)點(diǎn)采用如下設(shè)計(jì):

  • 將雙向綁定退化為單向綁定,View層消費(fèi)UI狀態(tài)流和事件流,這也意味著UI狀態(tài)的職責(zé)精簡,它不再承載View層的用戶輸入等事件
  • 將UI狀態(tài)獨(dú)立,Model層僅產(chǎn)生 UI狀態(tài)的局部變化事件

下圖為經(jīng)典的MVI原理示意圖:

談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟,Android,架構(gòu),移動開發(fā),架構(gòu),android,移動開發(fā),軟件框架,MVI

在上文中,我們已經(jīng)討論了各個角色的職責(zé),下面逐步展開討論角色具備的特性和細(xì)節(jié)知識。

在此之前,還請謹(jǐn)記:合適的才是最好的

沒有絕對的最好的設(shè)計(jì),只有最合適的設(shè)計(jì)。

再好的架構(gòu),都需要遵循其理念并結(jié)合項(xiàng)目因地制宜地進(jìn)行調(diào)整,以獲得最佳使用效果。所以請讀者諸君務(wù)必在閱讀時,結(jié)合自身項(xiàng)目的情況仔細(xì)思考以下問題:

  • 引入新框架所解決的痛點(diǎn)、衍生的問題、是否需要進(jìn)行框架調(diào)整?
  • 框架中的角色功能,為什么出現(xiàn),又有怎樣的局限?

單向數(shù)據(jù)流動

MVI擁抱了結(jié)構(gòu)復(fù)雜,但能夠靈活應(yīng)對業(yè)務(wù)編碼時的各種情況,按部就班即可。

從MVI原理圖中,可以清晰的看到 “數(shù)據(jù)” 的流動方向。 起始于 Intent,經(jīng)過分類和選擇性消費(fèi)后產(chǎn)生 Result,對應(yīng)的reducer函數(shù)計(jì)算后,得到最新的 State (以及裂變出必要的 Event,圖中未體現(xiàn)) ,驅(qū)動視圖。

注意:

  • 單向 是指 單一方向
  • 此處的 數(shù)據(jù) 是廣義的、寬泛的。
  • 僅描述數(shù)據(jù)流的 變化方向 ,與數(shù)據(jù)流的數(shù)量無關(guān),但一般 形成有效工作 均需要兩條數(shù)據(jù)流(上行數(shù)據(jù)流和下行數(shù)據(jù)流)

即驅(qū)動數(shù)據(jù)流變化的方向是唯一的,在英文中的術(shù)語為:Unidirectional Data Flow 簡稱 UDF。

MVC、MVP中的痛點(diǎn)

前文我們提到,在MVC和MVP中,著眼于 控制、調(diào)度 ,并不強(qiáng)調(diào) 數(shù)據(jù)流 的概念。

View和Model間之間的交互,一般有兩種編碼風(fēng)格:雙向的API調(diào)用、單向的API調(diào)用+回調(diào):

注意:以下兩圖并未體現(xiàn)Controller和Presenter細(xì)節(jié),僅表意,從View層出發(fā)的API調(diào)用和回到View層的UI更新

談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟,Android,架構(gòu),移動開發(fā),架構(gòu),android,移動開發(fā),軟件框架,MVI

雙向API調(diào)用如上圖。

談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟,Android,架構(gòu),移動開發(fā),架構(gòu),android,移動開發(fā),軟件框架,MVI

單向API調(diào)用+回調(diào)更新UI如上圖。

顯而易見,這兩種方式無法繼續(xù)抽象,需根據(jù)實(shí)際業(yè)務(wù)進(jìn)行命令式編碼。當(dāng)UI復(fù)雜時,難以寫出清晰、易讀的代碼,維護(hù)難度激增。

MVVM解決UI更新代碼混亂問題

前文我們已經(jīng)提到:MVVM中通過綁定框架,將UI事件轉(zhuǎn)化為數(shù)據(jù)變化,驅(qū)動業(yè)務(wù);業(yè)務(wù)結(jié)果表現(xiàn)為數(shù)據(jù)變化,驅(qū)動UI更新。

談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟,Android,架構(gòu),移動開發(fā),架構(gòu),android,移動開發(fā),軟件框架,MVI

顯而易見,維護(hù)樸素的數(shù)據(jù)要比直接維護(hù)復(fù)雜的UI要簡單。

但問題也同時產(chǎn)生,data1的變化有兩個可能的原因:

  • Model層業(yè)務(wù)結(jié)果使其變化,并期望它驅(qū)動UI更新
  • View層發(fā)生事件,反饋數(shù)據(jù)變化,并期望它驅(qū)動Model層邏輯

因此,框架需要考慮標(biāo)識數(shù)據(jù)變化來源、或者其他手段消除方向性所帶來的問題。

并且MVVM難以靈活決定的 “何時調(diào)用Model層邏輯”,即大多數(shù)業(yè)務(wù)中,都需要結(jié)合多個屬性的變化形成組合條件來驅(qū)動Model層邏輯。

本篇并不重點(diǎn)討論MVVM,故不再展開MVVM解決循環(huán)更新的方案,以及衍生的問題。

盡管如此,MVVM中的數(shù)據(jù)綁定依舊解決了View層更新繁雜的問題。

用Intent靈活決定何時調(diào)用Model

既然數(shù)據(jù)驅(qū)動UI有極大的益處,且View層事件驅(qū)動ViewModel的數(shù)據(jù)變化有很多弊端 (需要建立很高的復(fù)雜度) ,那自然需要 趨利避害

談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟,Android,架構(gòu),移動開發(fā),架構(gòu),android,移動開發(fā),軟件框架,MVI

僅保留數(shù)據(jù)驅(qū)動UI的部分,并增加Intent用以驅(qū)動Model層業(yè)務(wù)

在于 MVC/MVP 以及 MVVM 對比后不難得出結(jié)論:

  • MVC/MVP中,View層通過調(diào)用C/P層API的方式最終調(diào)用到Model層業(yè)務(wù),方式質(zhì)樸、無難度。但業(yè)務(wù)量規(guī)模增大后接口方法數(shù)也會增多,導(dǎo)致C/P層尾大不掉,難以重用。
  • MVVM中,VM層總是需要利用 技巧 進(jìn)行模型概念轉(zhuǎn)換,以滿足業(yè)務(wù)響應(yīng)滿足實(shí)際需求,需要很深厚的設(shè)計(jì)經(jīng)驗(yàn)才能寫出非常優(yōu)秀的代碼,這并不友好。

作者按:我個人認(rèn)為一個友好的設(shè)計(jì),不應(yīng)當(dāng)劍走偏鋒,而應(yīng)當(dāng)大巧不工,能夠以力破法,達(dá)成 “使用者只需要吃透理論就可以解決各類問題” 的目標(biāo)。

而MVI在架構(gòu)角色中設(shè)計(jì)了Intent的角色:

  • 它包含了業(yè)務(wù)調(diào)用的意圖和數(shù)據(jù)
  • 從設(shè)計(jì)上可滿足 調(diào)用實(shí)現(xiàn) 的分離
  • 架構(gòu)模型中以Intent流的形式出現(xiàn),下游對其的 篩選轉(zhuǎn)換 、 消費(fèi) 等行為可遵循 FP范式 (即函數(shù)式編程范式、Functional Programming Patterns) ,邏輯的復(fù)用粒度為方法級,復(fù)用度更高更靈活
  • 解決了MVVM中的方向性問題、MVC/MVP 中的靈活度問題等

單一可信數(shù)據(jù)源

我猜測讀者諸君都曾聽過這個詞,將 單一可信數(shù)據(jù)源 拆解一下:

  • 單一
  • 可信
  • 數(shù)據(jù)源

在MVI背景下,數(shù)據(jù)源 指的是視圖對應(yīng)的數(shù)據(jù)實(shí)體,它代表視圖的內(nèi)容狀態(tài)。

可信指從數(shù)據(jù)源中獲取的數(shù)據(jù)是 最新的、完整的、可靠的,否則是不可信的,我們沒有理由在編碼中使用不可信的數(shù)據(jù)源

單一是指這樣的數(shù)據(jù)源僅一個。

在經(jīng)典設(shè)計(jì)中,其內(nèi)涵如下圖:

談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟,Android,架構(gòu),移動開發(fā),架構(gòu),android,移動開發(fā),軟件框架,MVI

  • 按照視圖的 所有的 內(nèi)容狀態(tài),定義一個不可變的 ViewState
  • 按照業(yè)務(wù)初始化 ViewState 實(shí)例
  • Model業(yè)務(wù)生成驅(qū)動 ViewState變化的Result
  • 計(jì)算出新狀態(tài),Reduce(Pre-ViewState,Result) -> New-ViewState
  • 更新數(shù)據(jù)源
  • View層消費(fèi)ViewState

借助于數(shù)據(jù)綁定框架,可以很方便地解決視圖更新的問題。

想象一下,此時頁面UI非常復(fù)雜……

如果僵化的信奉這樣的 單一 ,情況會如何呢?

  • 復(fù)雜(大量屬性)的ViewState
  • 復(fù)雜的UI更新計(jì)算,e.g. 100個屬性變了2個,依然需要計(jì)算98個屬性未變或者全量強(qiáng)制更新

在 APP-A和APP-B中,我分別使用了 DataBinding和Compose,但均無法避免該問題。

何為單一

從機(jī)器執(zhí)行程序的原理上看,我們無法實(shí)現(xiàn) 多個內(nèi)容一致的數(shù)據(jù)源任意時刻 滿足 最新的、可靠的。

將視圖視為一個整體,規(guī)定它只擁有 一個 可信的數(shù)據(jù)源。在此基礎(chǔ)上看局部的視圖,它們也順其自然地僅擁有一個可信的數(shù)據(jù)源。

反過來看,當(dāng)任意的局部視圖僅具有一個可信數(shù)據(jù)源時,整體視圖也僅有一個邏輯上的可信數(shù)據(jù)源。

據(jù)此,我們可以對 經(jīng)典MVI實(shí)現(xiàn) 進(jìn)行一定程度的改造,將ViewState進(jìn)行局部分解,使得UI綁定部分的業(yè)務(wù)邏輯更 清晰、干凈。

請注意,復(fù)雜度不會憑空消失,我們?yōu)榱俗?“UI綁定的業(yè)務(wù)邏輯更清晰、干凈”、“更新UI的計(jì)算量更少”,將復(fù)雜度轉(zhuǎn)移到了ViewState的拆分。拆分后,將具有 多個視圖部件的單一可信數(shù)據(jù)源,注意,為了不引起額外的麻煩、并且便于維護(hù)擴(kuò)展,建議遵守以下條件:

  • 基于業(yè)務(wù)需求,組合數(shù)據(jù)源形成新數(shù)據(jù)源
  • 不在數(shù)據(jù)源的邏輯范圍之外進(jìn)行數(shù)據(jù)源組合操作

舉個虛擬的例子:用戶需要實(shí)名認(rèn)證 且 關(guān)注博主 ,才在界面上顯示某功能按鈕。下面使用代碼分別演示。

考慮到RxJava的廣泛度依舊高于Kotlin-Coroutine+flow,數(shù)據(jù)流的實(shí)現(xiàn)采用RxJava

注意,考慮到讀者可能會編寫demo做UDF局部的驗(yàn)證,下文中的代碼以示例目的為主,兼顧編寫場景冒煙的方便性,流的類型不一定是構(gòu)建完整UDF的最佳選擇。

經(jīng)典實(shí)現(xiàn)

在經(jīng)典MVI實(shí)現(xiàn)中,需要先定義ViewState

data class ViewState(
    /*unique id of current login user*/
    val userId: Int,
    /*true if the current login user has complete real-name verified*/
    val realNameVerified: Boolean,
    /*true if the current login user has followed the author*/
    val hasFollowAuthor: Boolean
) {
}

并定義ViewModel,創(chuàng)建ViewState流,忽略掉其初始化和其他部分

class VM {
    val viewState = BehaviorSubject.create<ViewState>()

    //ignore
}

并定義View層,忽略掉其他部分,簡單起見暫時不使用數(shù)據(jù)綁定框架

class View {

    private val vm = VM()
    lateinit var imgRealNameVerified: ImageView
    lateinit var cbHasFollowAuthor: CheckBox
    lateinit var someButton: Button

    fun onCreate() {
        //ignore view initialize

        vm.viewState.subscribe {
            render(it)
        }
    }

    private fun render(state: ViewState) {
        imgRealNameVerified.isVisible = state.realNameVerified
        cbHasFollowAuthor.isChecked = state.hasFollowAuthor
        someButton.isVisible = state.realNameVerified && state.hasFollowAuthor

        //ignore other
    }
}

在JS中,JSON并不能附加邏輯,基本等價于Java中的POJO,故在數(shù)據(jù)源外部處理簡單邏輯的情況較為常見。而在Java、Kotlin中可以進(jìn)行適當(dāng)?shù)膬?yōu)化,適當(dāng)封裝,使得代碼更加干凈便于維護(hù):

data class ViewState(
    //ignore
) {
    fun isSomeFuncEnabled():Boolean = realNameVerified && hasFollowAuthor
}

class View {
    //ignore
    
    private fun render(state: ViewState) {
        //...
        
        someButton.isVisible = state.isSomeFuncEnabled()
    }
}
拆分實(shí)現(xiàn)

依舊先定義邏輯上完整的ViewState:

class ComposedViewState(
    /*unique id of current login user*/
    val userId: Int,
) {

    /**
     * real-name-verified observable subject,feed true if the current login user has complete real-name verified
     * */
    val realNameVerified = BehaviorSubject.create<Boolean>()

    /**
     * follow-author observable subject, feed true if the current login user has followed the author
     * */
    val hasFollowAuthor = BehaviorSubject.create<Boolean>()

    val someFuncEnabled = BehaviorSubject.combineLatest(realNameVerified, hasFollowAuthor) { a, b -> a && b }
}

定義ViewModel,子模塊數(shù)據(jù)流均已定義,故而無需再定義全ViewState的流

class VM(val userId: Int) {
    val viewState = ComposedViewState(userId)
    //ignore
}

編寫View層的UI綁定,同樣簡單起見,不使用數(shù)據(jù)綁定框架

class View {

    private val vm = VM(1)
    lateinit var imgRealNameVerified: ImageView
    lateinit var cbHasFollowAuthor: CheckBox
    lateinit var someButton: Button

    fun onCreate() {
        //ignore view initialize
        bindViewStateWithUI()
    }

    private fun bindViewStateWithUI() {
        vm.viewState.realNameVerified.subscribe {
            renderSection1(it)
        }

        vm.viewState.hasFollowAuthor.subscribe {
            renderSection2(it)
        }

        vm.viewState.someFuncEnabled.subscribe {
            renderSection3(it)
        }
        //...
    }

    private fun renderSection1(foo:Boolean) {
        imgRealNameVerified.isVisible = foo
    }

    private fun renderSection2(foo:Boolean) {
        cbHasFollowAuthor.isChecked = foo
    }

    private fun renderSection3(foo:Boolean) {
        someButton.isVisible = foo
    }
}

例子較為簡單,在實(shí)際項(xiàng)目中,如果遇到復(fù)雜頁面,則可以分塊進(jìn)行處理。

注意:實(shí)際情況中,并沒有必要將每一個子數(shù)據(jù)源拆分到一個View級別的控件,那樣過于啰嗦,例子因非常簡單而無法豐滿起來。 e.g. 針對每一塊視圖區(qū),例如作者區(qū)域,定義子ViewState類,創(chuàng)建其數(shù)據(jù)流即可。

作者按:務(wù)必評估,在一次Model業(yè)務(wù)產(chǎn)生的Result中,會引起數(shù)據(jù)流下游的更新次數(shù)。 為避免產(chǎn)生不可預(yù)期的問題,可通過類似以下方式,使下游響應(yīng)次數(shù)表現(xiàn)和經(jīng)典實(shí)現(xiàn)的情況一致。

額外定義PartialChange流或者功能等價的流,它用于標(biāo)識 reduce 計(jì)算的開始和結(jié)束,可以將此期間的數(shù)據(jù)流的變化延遲到最后發(fā)送終態(tài)

更加推薦定義功能上等價的流

class ComposedViewState(
    /*unique id of current login user*/
    val userId: Int,
) {

    internal val changes = BehaviorSubject.create<PartialChange>()
    
    //ignore

    val someFuncEnabled =
        BehaviorSubject.combineLatest(realNameVerified, hasFollowAuthor) { a, b -> a && b }.sync(PartialChange.Tag, changes)
}

inline fun <reified T, S> Observable<T>.sync(tag: S, sync: BehaviorSubject<S>): Observable<T> {
    return BehaviorSubject.combineLatest(this, sync) { source, syncItem ->
        if (syncItem == tag) {
            syncItem
        } else {
            source
        }
    }.filter { it is T }.cast(T::class.java)
}

修改PartialChange,為reduce函數(shù)添加邊界:

PartialChange是Model產(chǎn)生的Result的表現(xiàn)物,封裝了ViewState的reduce函數(shù)邏輯,即如何從 Pre-ViewState 生成 新 ViewState

sealed class PartialChange {
    open fun reduce(state: ComposedViewState) {

    }

    /**
     * 同步標(biāo)記,從頭開始到真實(shí)PartialChange之間,流的狀態(tài)生效
     * */
    object Tag : PartialChange()

    object None : PartialChange()

    class Foo(val a: Boolean, val b: Boolean) : PartialChange() {
        override fun reduce(state: ComposedViewState) {
            state.changes.onNext(Tag)
            state.realNameVerified.onNext(a)
            state.hasFollowAuthor.onNext(b)
            state.changes.onNext(this)
        }
    }
}

要想優(yōu)雅,需要工具

采用響應(yīng)式流,避免命令式編碼

想來這一點(diǎn)已不需要多做解釋。

在Android中,存在 LiveData 組件,它通過簡單的方式封裝了可觀測的數(shù)據(jù),但實(shí)現(xiàn)方式簡單也限制了它的功能 不夠強(qiáng)大 。因此,建議使用 RxJava 或者 Kotlin-Coroutine & flow 構(gòu)建數(shù)據(jù)流。

本節(jié)便不再展開。

采用數(shù)據(jù)綁定框架

采用 jetpack-compose 或者 DataBinding 均可以移除枯燥的UI命令式邏輯,在APP-A中我使用了DataBinding,在APP-B中我使用了Compose。

在 ViewState的代碼很棒時,均可以獲得優(yōu)秀的編程體驗(yàn),從啰嗦的UI中解放出來。

作者的個人觀點(diǎn):

關(guān)于Compose。Compose依舊屬于較新的事物,在商業(yè)項(xiàng)目中使用存在學(xué)習(xí)門檻和造輪工作。在目標(biāo)用戶具有較高容忍度的情況下,已然可以進(jìn)行嘗試。

關(guān)于DataBinding。一個近乎毀譽(yù)參半的工具,關(guān)于它的批判,大多集中于:xml中實(shí)現(xiàn)的邏輯難以閱讀、維護(hù),這實(shí)際上是對DataBinding設(shè)計(jì)的誤解而帶來的錯誤使用。

DataBinding本身具有生成VM層的功能,但這一功能并不足夠強(qiáng)大,且沒有完善的使用指導(dǎo),而在官方Demo中過度宣傳了它,導(dǎo)致大家認(rèn)為DataBinding就該這樣使用。

僅使用基礎(chǔ)的數(shù)據(jù)綁定功能、和Resource或者Context有關(guān)的功能(例如字符串模板)、組件生命周期綁定等,適度自定義綁定。

何為狀態(tài)、何為事件。最后的一公里

首先區(qū)別于上文提到的UI事件,這里的狀態(tài)和事件均產(chǎn)生于數(shù)據(jù)流的末段,而UI事件處于數(shù)據(jù)流的首段。

UI事件屬于:A possible action that the user can perform that is monitored by an application or the operating system (event listener). When an event occurs an event handler is called which performs a specific task

在展開之前,先用一張圖回顧總結(jié)上文中對于 單向數(shù)據(jù)流 & 單一可信數(shù)據(jù)源 的知識

談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟,Android,架構(gòu),移動開發(fā),架構(gòu),android,移動開發(fā),軟件框架,MVI

單向數(shù)據(jù)流動 章節(jié)中,提到了MVI的UDF設(shè)計(jì):

  • 系統(tǒng)捕獲的UI事件、其他偵聽事件(例如熄屏、應(yīng)用生命周期事件),生成Intent,壓入Intent流中
  • ViewModel層中篩選、轉(zhuǎn)換、處理Intent,實(shí)際是使用Model層業(yè)務(wù),產(chǎn)生業(yè)務(wù)結(jié)果,即PartialChange
  • PartialChange經(jīng)過Reducer計(jì)算處理得到最新的ViewState,壓入ViewState流
  • View層(廣義的表現(xiàn)層)響應(yīng)并呈現(xiàn)最新的ViewState

單一可信數(shù)據(jù)源 章節(jié)中,提到View層應(yīng)當(dāng)采用 單一可信數(shù)據(jù)源

在這張圖中,我們僅體現(xiàn)了 狀態(tài) 即 ViewState。

關(guān)于GUI程序的認(rèn)知

在展開前,先聊點(diǎn)理念上的內(nèi)容。請讀者諸君思考下自己對于GUI程序的認(rèn)知。

作者的理解:

程序狹義上是計(jì)算機(jī)能識別和執(zhí)行的一組指令集,編程工作是在程序世界對 客觀實(shí)體 、 業(yè)務(wù)邏輯 進(jìn)行 建模和邏輯表達(dá)。

而GUI程序擁有 用戶圖形界面 , 除了結(jié)合硬件接收用戶交互輸入外,可以將 程序世界中的模型用戶圖形界面 等方式表現(xiàn)給用戶。

表現(xiàn)出來的內(nèi)容代表著客觀實(shí)體

其本質(zhì)目的在于:通過 描述特征屬性 、 描述變化過程 等方式讓用戶感知并理解 客觀實(shí)體

而除了通過 程序語言描述 、 程序世界模擬展現(xiàn) 外,同樣可以通過 自然語言描述 達(dá)到目的,這也是產(chǎn)品經(jīng)理的工作。

當(dāng)然,產(chǎn)品經(jīng)理往往需要借助一些工具來提升自己的自然語言表達(dá)能力,但無奈的是能用數(shù)學(xué)公式和邏輯推演表達(dá)需求的產(chǎn)品經(jīng)理太少見了。

寫這段只是為了引入 他山之石 。

First-Order logic

在數(shù)學(xué)、哲學(xué)、語言學(xué)、計(jì)算機(jī)科學(xué)中,有一個概念 First-Order logic,無論是產(chǎn)品需求還是計(jì)算機(jī)程序,都可以建立FOL表達(dá)。

當(dāng)然,本篇不討論FOL,那是一個很龐大且偏離主題的事情。我僅僅是想借用其中的概念。

FOL表達(dá) Event或者State時:

  • Event 體現(xiàn)的是特定的變化
  • State 體現(xiàn)的是客觀實(shí)體在任意時刻都適用的一組情況,即一段時間內(nèi)無變化的條件或者特征

不難理解,變化是瞬時的,連續(xù)的變化是可分的。

但在人機(jī)交互中,瞬時意義很小,我們的目的在于讓用戶感知。

例如:“好友向你發(fā)送了一條消息的場景中”,消息抵達(dá)就是Event,它背后潛藏著 “消息數(shù)的變化”、“最新消息內(nèi)容的變化” 等。 在常見的設(shè)計(jì)中:

  • 應(yīng)用需要彈出一個氣泡通知用戶這一事件
  • 應(yīng)用需要更新消息數(shù),消息列表內(nèi)容等,以呈現(xiàn)出最新的State

而為了讓用戶感知到,氣泡呈現(xiàn)時長并不是瞬時的,但在產(chǎn)品交互設(shè)計(jì)中依舊將其定義為事件。

分離狀態(tài)和事件,不是吃飽撐得

看山是山、看水是水

此時此刻,答案已經(jīng)很明顯。

在通用的產(chǎn)品設(shè)計(jì)中,狀態(tài)和事件有不同的意義,如果程序中不分離出兩者,則必然是自找麻煩,這是公然挑釁 面向?qū)ο缶幊?/code> 的行為。如果不明確定義不同的Class,則勢必導(dǎo)致代碼混亂不堪,畢竟這是違背編程原則的事情。

在大多MVVM設(shè)計(jì)中,狀態(tài)和事件未分家,導(dǎo)致bug叢生,這一點(diǎn)便不再展開。

如何區(qū)分Event和State

State是一段時間內(nèi)無變化的條件或者特征,它天然的 契合 了位于表現(xiàn)層的主體內(nèi)容所對應(yīng)的 數(shù)據(jù)模型特征。

Event是特定的變化,它在表現(xiàn)層體現(xiàn),但與State的生命周期不一致,且并無一一對應(yīng)的關(guān)系。

基于經(jīng)驗(yàn)主義,我們可以機(jī)械地、籠統(tǒng)地認(rèn)為:頁面主體靜態(tài)內(nèi)容所需要的數(shù)據(jù)屬于State范疇,氣泡提醒等短暫的物體所需要的數(shù)據(jù)屬于Event范疇。

從邏輯推演的角度出發(fā),進(jìn)行 等價邏輯推斷條件限定下的邏輯推斷 ,一定序列的Event可以模型轉(zhuǎn)換為State。

事件粘性導(dǎo)致重復(fù)?只是框架設(shè)計(jì)的bug

看山不是山,看水不是水

前面提到,State是一段時間內(nèi)無變化的條件或者特征,所以在程序設(shè)計(jì)中State具有粘性的特征。

如果Event也設(shè)計(jì)出這樣的粘性特征并造成重復(fù)消費(fèi),明顯是違背需求的,無疑是框架設(shè)計(jì)的Bug。此問題在各大論壇中很常見。

注意,我們無法脫離實(shí)際需求去二元化的討論事件本身該不該有粘性特征,只能結(jié)合實(shí)際討論框架功能是否存在bug

如果要實(shí)現(xiàn)以力破法,在框架設(shè)計(jì)層面上 Event體系的設(shè)計(jì)要比State體系要復(fù)雜 。因?yàn)閺慕换ピO(shè)計(jì)上:

  • State 只需要考慮呈現(xiàn)的準(zhǔn)確性和及時性,除去美觀、可理解性等等
  • Event 需要考慮準(zhǔn)確性、優(yōu)先級、及時性、按條件丟棄等等,除去美觀、可理解性等等

舉個例子:網(wǎng)絡(luò)連接問題導(dǎo)致的Web-API調(diào)用失敗需要使用Toast提示網(wǎng)絡(luò)連接失敗

不難想象:

  • 可能一瞬間的斷開網(wǎng)絡(luò)連接,會導(dǎo)致多個連接均返回失敗
  • 可能連接問題未修復(fù),10秒前請求失敗,當(dāng)前請求又失敗了

難道連續(xù)彈出嗎?難道和上一次Event一致就不消費(fèi)嗎?…

或許您會使用一些 劍走偏鋒的技巧 來解決問題,但技巧總是建立在特定條件下生效的,一旦條件發(fā)生變化,就會帶來煩惱,您很難控制上游的PM和交互設(shè)計(jì)師。

所以在框架層面需要針對產(chǎn)品、交互設(shè)計(jì)的泛化理念,設(shè)計(jì)準(zhǔn)確的、靈活的Event體系。

準(zhǔn)確的、靈活的Event體系

看山還是山,看水還是水

回到FOL中,為了更加準(zhǔn)確的表達(dá)Event和State的含義,還需要一些額外的參數(shù),例如:參與者地點(diǎn)、時間 等。

想通這一點(diǎn)會發(fā)現(xiàn),產(chǎn)品中定義的Event事件、及其消費(fèi)邏輯均含有隱藏屬性,例如:

  • 發(fā)生時間
  • 客觀有效期
  • 判斷有效的條件(如呈現(xiàn)的條件)
  • 判斷失效的條件 ,用于實(shí)現(xiàn)提前失效

產(chǎn)品經(jīng)理和交互設(shè)計(jì)師一般會使用 “響應(yīng)時間”、“優(yōu)先級” 等詞描述它們,但一般不嚴(yán)謹(jǐn)、不成體系,帶來期望不一致的問題

反觀State流,它代表了界面主體內(nèi)容在時間軸上的完整變化,任意一個時間點(diǎn)均可以得出界面內(nèi)容所對應(yīng)的條件和特征。一旦State流中出現(xiàn)一個新的狀態(tài),它均被及時的、準(zhǔn)確的在表現(xiàn)層予以體現(xiàn)。

不難理解,一個State的生命周期為 從init或者reducer計(jì)算生成開始reducer計(jì)算出新State、宿主生命期結(jié)束為止,在State流中已然暗含:

  • State之間無生命周期重疊
  • 所有State的生命周期相加可填滿時間軸

前文提到Event是瞬時的,所以Event本身并沒有實(shí)質(zhì)意義上的生命周期,為了方便表述,我們將 “Event從生成到在表現(xiàn)層不可觀測的階段” 定義為Event生命周期

而Event流 不同于 State流 ,因?yàn)镋vent的生命周期情況更加復(fù)雜:

  • Event可能存在生命周期重疊
  • 所有Event的生命周期相加可能無法覆蓋完整的時間軸

需要額外設(shè)計(jì)實(shí)現(xiàn) 。實(shí)現(xiàn)這一點(diǎn)后,從Event流中分流(以及裂變+組合)出的 子流 將和State流 性質(zhì)一致。

此刻,您會發(fā)現(xiàn),根據(jù)不同類型的事件交互控件所對應(yīng)的交互特征,又將Event流結(jié)合條件流衍生出各個State流。完整的數(shù)據(jù)流細(xì)節(jié)如下:

談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟,Android,架構(gòu),移動開發(fā),架構(gòu),android,移動開發(fā),軟件框架,MVI

Android 學(xué)習(xí)筆錄

Android 性能優(yōu)化篇:https://qr18.cn/FVlo89
Android 車載篇:https://qr18.cn/F05ZCM
Android 逆向安全學(xué)習(xí)筆記:https://qr18.cn/CQ5TcL
Android Framework底層原理篇:https://qr18.cn/AQpN4J
Android 音視頻篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(內(nèi)含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源碼解析筆記:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知識體:https://qr18.cn/CyxarU
Android 核心筆記:https://qr21.cn/CaZQLo
Android 往年面試題錦:https://qr18.cn/CKV8OZ
2023年最新Android 面試題集:https://qr18.cn/CgxrRy
Android 車載開發(fā)崗位面試習(xí)題:https://qr18.cn/FTlyCJ
音視頻面試題錦:https://qr18.cn/AcV6Ap文章來源地址http://www.zghlxwxcb.cn/news/detail-643764.html

到了這里,關(guān)于談一談在兩個商業(yè)項(xiàng)目中使用MVI架構(gòu)后的感悟的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 談一談緩存穿透,擊穿,雪崩

    談一談緩存穿透,擊穿,雪崩

    緩存穿透是指在使用緩存系統(tǒng)時,頻繁查詢一個不存在于緩存中的數(shù)據(jù),導(dǎo)致這個查詢每次都要通過緩存層去查詢數(shù)據(jù)源,無法從緩存中獲得結(jié)果。這種情況下,大量的請求會直接穿透緩存層,直接訪問數(shù)據(jù)源,從而增加了系統(tǒng)的負(fù)載,降低了系統(tǒng)的性能。 通常情況下,當(dāng)一

    2024年02月14日
    瀏覽(21)
  • 談一談Python中的裝飾器

    談一談Python中的裝飾器

    1.1 何為Python中的裝飾器? Python中裝飾器的定義以及用途: 裝飾器是一種特殊的函數(shù),它可以接受一個函數(shù)作為參數(shù),并返回一個新的函數(shù)。裝飾器可以用來修改或增強(qiáng)函數(shù)的行為,而不需要修改函數(shù)本身的代碼。在Python中,裝飾器通常用于實(shí)現(xiàn)AOP(面向切面編程),例如日

    2023年04月16日
    瀏覽(26)
  • 談一談冷門的C語言爬蟲

    談一談冷門的C語言爬蟲

    C語言可以用來編寫爬蟲程序,但是相對于其他編程語言,C語言的爬蟲開發(fā)可能會更加復(fù)雜和繁瑣。因?yàn)镃語言本身并沒有提供現(xiàn)成的爬蟲框架和庫,需要自己編寫網(wǎng)絡(luò)請求、HTML解析等功能。 不過,如果你對C語言比較熟悉,也可以嘗試使用C語言編寫爬蟲程序,這樣可以更好地

    2024年02月08日
    瀏覽(60)
  • 【大數(shù)據(jù)面試題】007 談一談 Flink 背壓

    一步一個腳印,一天一道面試題 (有些難點(diǎn)的面試題不一定每天都能發(fā),但每天都會寫) 在流式處理框架中,如果下游的處理速度,比上游的輸入數(shù)據(jù)小,就會導(dǎo)致程序處理慢,不穩(wěn)定,甚至出現(xiàn)崩潰等問題。 上游數(shù)據(jù)突然增大 比如數(shù)據(jù)源突然數(shù)據(jù)量增大多倍,下游處理速

    2024年02月20日
    瀏覽(19)
  • 談一談Vue怎么用extend動態(tài)創(chuàng)建組件

    Vue.js是一個流行的JavaScript框架,它提供了許多功能來幫助我們構(gòu)建交互式Web應(yīng)用程序。其中之一是使用extend方法動態(tài)創(chuàng)建組件。 ? extend方法是Vue.js提供的一個方法,它允許我們創(chuàng)建一個新的Vue組件構(gòu)造函數(shù)。這個新的構(gòu)造函數(shù)可以繼承現(xiàn)有的組件,也可以添加新的選項(xiàng)。 我

    2023年04月24日
    瀏覽(22)
  • 談一談SQLite、MySQL、PostgreSQL三大數(shù)據(jù)庫

    談一談SQLite、MySQL、PostgreSQL三大數(shù)據(jù)庫

    每一份付出,必將有一份收貨,就像這個小小的果實(shí),時間到了,也就會開花結(jié)果… SQLite、MySQL 和 PostgreSQL 都是流行的關(guān)系型數(shù)據(jù)庫管理系統(tǒng)(RDBMS),但它們在功能、適用場景和性能方面有一些不同。 SQLite : 輕量級 : SQLite 是一個嵌入式數(shù)據(jù)庫,它不需要一個獨(dú)立的數(shù)據(jù)庫

    2024年02月05日
    瀏覽(31)
  • [輕科普]談一談最近手機(jī)上的2億像素

    [輕科普]談一談最近手機(jī)上的2億像素

    最近很多廠商發(fā)布了2億像素的手機(jī),2億像素比較火熱,如realme 11 pro + ,榮耀的honor 90 pro,以及之前小米發(fā)布的Redmi note 12 pro +。 下圖為honor 90 Pro上搭載的2億像素 ,為S5KHP3 下圖為 紅米上搭載的S5kHPX 2億像素傳感器。? ? 下圖為 Realme的兩億像素,S5KHP3的超級變焦版本 ? 以上三

    2024年02月06日
    瀏覽(18)
  • 【談一談】: 我們工作中的單例模式有哪些寫法?

    【談一談】: 我們工作中的單例模式有哪些寫法?

    我們要實(shí)現(xiàn)一個單例,首先最重要的是什么? 當(dāng)然是把構(gòu)造函數(shù)私有化,變成 private 類型,(為啥? 單例單例,如果誰都能通過構(gòu)造函數(shù)創(chuàng)建對象,還叫單例嗎?是不~) 嗯~我們構(gòu)造函數(shù)私有化后,我們應(yīng)該 操作啥 呢? 接著我們需要提供 一個方法 ,這個方法要保證初始化 有且僅 初始化 一

    2024年02月21日
    瀏覽(27)
  • 【12期】談一談redis兩種持久化機(jī)制的區(qū)別?

    RDB方案可以在規(guī)定時間間隔內(nèi)創(chuàng)建數(shù)據(jù)集的時間點(diǎn)快照。 AOF方案記錄了服務(wù)器執(zhí)行的所有寫操作命令,并在服務(wù)器啟動時通過重新執(zhí)行這些命令來還原數(shù)據(jù)集。AOF文件完全遵循Redis協(xié)議格式保存,新命令會被追加到文件末尾。此外,Redis還能在后臺對AOF文件重寫以確保不超過

    2024年02月11日
    瀏覽(31)
  • 【大數(shù)據(jù)面試題】008 談一談 Flink Slot 與 并行度

    一步一個腳印,一天一道面試題 該文章有較多引用文章 https://zhuanlan.zhihu.com/p/572170629?utm_id=0 并行度 Parallelism 概念作用 并行度是作用于 算子 的單位。Flink 的每個算子都可以單獨(dú)設(shè)置并行度。一般來說,并行度越大,處理能力越大,處理的就越快。 Slot 概念作用 Slot 是 Flink

    2024年02月19日
    瀏覽(29)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包