安卓應(yīng)用開發(fā)中的MVC、MVP、MVVM、MVI(2)

1. MVI架構(gòu)簡介

MVI是Model-View-Intent的縮寫,是一種最新的安卓應(yīng)用開發(fā)架構(gòu)模式,受到了Cycle.js框架中單向數(shù)據(jù)流和循環(huán)性質(zhì)的啟發(fā)。MVI與其它常見的架構(gòu)模式,如MVC、MVP或MVVM,有著很大的不同。

1.1 MVI架構(gòu)的組成

MVI架構(gòu)由三個主要組件組成:Model、View和Intent。

  • Model:表示應(yīng)用程序的狀態(tài),包括數(shù)據(jù)、用戶界面和業(yè)務(wù)邏輯。Model是不可變的,只能通過Intent來改變。
  • View:負責(zé)渲染Model,并將用戶操作轉(zhuǎn)換為Intent。
  • Intent:表示用戶或系統(tǒng)對應(yīng)用程序狀態(tài)的改變意圖。Intent是唯一能夠觸發(fā)Model更新的方式。

1.2 MVI架構(gòu)的工作流程

MVI架構(gòu)遵循一個單向數(shù)據(jù)流和循環(huán)反饋機制。其工作流程如下:

  • 用戶或系統(tǒng)觸發(fā)一個事件,例如點擊按鈕、滑動屏幕或接收通知。
  • View將事件轉(zhuǎn)換為一個Intent,并發(fā)送給Model。
  • Model接收到Intent后,根據(jù)業(yè)務(wù)邏輯處理并更新自身狀態(tài)。
  • Model將更新后的狀態(tài)發(fā)送給View。
  • View根據(jù)Model渲染界面。

這個過程不斷重復(fù),形成一個閉環(huán)。

2. MVI架構(gòu)在安卓開發(fā)中的優(yōu)勢

MVI架構(gòu)在安卓開發(fā)中有以下幾個優(yōu)勢:

2.1 易于測試

由于MVI架構(gòu)中Model是不可變且獨立于View和Intent的,因此可以方便地對其進行單元測試。同時,由于View只負責(zé)渲染Model,并不涉及任何業(yè)務(wù)邏輯或狀態(tài)管理,因此也可以輕松地對其進行UI測試。

2.2 易于調(diào)試

由于MVI架構(gòu)中所有狀態(tài)改變都是通過Intent來觸發(fā)并記錄在Model中的,因此可以方便地追蹤和重現(xiàn)任何問題或異常。同時,由于Model是不可變且唯一確定界面顯示內(nèi)容和行為的源頭,因此可以避免出現(xiàn)視圖層級混亂或數(shù)據(jù)不一致等問題。

2.3 易于維護

由于MVI架構(gòu)中各個組件之間有明確且簡單的職責(zé)劃分,并且遵循單向數(shù)據(jù)流原則,因此可以降低代碼復(fù)雜度和耦合度,并提高代碼可讀性和可擴展性。

3. MVI架構(gòu)在安卓開發(fā)中的實踐

為了更好地理解和掌握MVI架構(gòu)在安卓開發(fā)中如何實踐,在本節(jié)我們將以一個簡單而實用的計算器應(yīng)用為例

3.1 創(chuàng)建項目和布局文件

首先,我們需要創(chuàng)建一個安卓項目,并在activity_main.xml文件中定義計算器應(yīng)用的界面布局。布局文件的內(nèi)容如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="end"
        android:textSize="32sp" />

    <GridLayout
        android:id="@+id/gl_buttons"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:columnCount="4">

        <Button
            android:id="@+id/btn_clear"
            style="@style/Widget.AppCompat.Button.Borderless.Colored"
            android:text="@string/clear" />

        <Button
            style="@style/Widget.AppCompat.Button.Borderless.Colored" />

        <Button
            style="@style/Widget.AppCompat.Button.Borderless.Colored" />

        <Button
            style="@style/Widget.AppCompat.Button.Borderless.Colored" />

        <Button
            style="@style/Widget.AppCompat.Button.Borderless.Colored" />

        <Button
            style="@style/Widget.AppCompat.Button.Borderless.Colored" />

        <Button
            style="@style/Widget.AppCompat.Button.Borderless.Colored" />

        <Button
            style="@style/Widget.AppCompat.Button.Borderless.Colored" />
        
       <!-- 省略部分按鈕代碼 -->

    </GridLayout>

</LinearLayout>

這段代碼定義了一個垂直方向的線性布局,其中包含一個顯示結(jié)果的文本視圖和一個包含16個按鈕的網(wǎng)格布局。每個按鈕都有一個唯一的id和文本,分別對應(yīng)數(shù)字、運算符或清除功能。

3.2 定義Model類

接下來,我們需要定義Model類,用于表示計算器應(yīng)用的狀態(tài)。Model類的內(nèi)容如下:

// Model.kt

data class Model(
    val expression: String = "", // 表達式字符串,例如 "2+3*4-5="
    val result: String = "", // 結(jié)果字符串,例如 "9.0"
    val error: String = "" // 錯誤信息字符串,例如 "Invalid input."
)

這段代碼定義了一個數(shù)據(jù)類Model,其中包含三個屬性:expression、result和error。expression表示用戶輸入或顯示的表達式字符串;result表示計算出或顯示的結(jié)果字符串;error表示發(fā)生或顯示的錯誤信息字符串。這三個屬性都有默認值為空字符串。

3.3 定義Intent類

然后,我們需要定義Intent類,用于表示用戶或系統(tǒng)對計算器應(yīng)用狀態(tài)改變的意圖。Intent類是一個密封類(sealed class),其內(nèi)容如下:

// Intent.kt

sealed class Intent {
    object Clear : Intent() // 清除意圖,表示用戶點擊了清除按鈕或系統(tǒng)初始化時觸發(fā)。
    data class Append(val value: String) : Intent() // 追加意圖,表示用戶點擊了數(shù)字或運算符按鈕時觸發(fā)。
    object Evaluate : Intent() // 計算意圖,表示用戶點擊了等號按鈕時觸發(fā)。
}

這段代碼定義了一個密封類Intent,并聲明了三個子類:Clear、Append和Evaluate。Clear表示清除意圖,即用戶點擊了清除按鈕或系統(tǒng)初始化時觸發(fā);Append表示追加意圖,即用戶點擊了數(shù)字或運算符按鈕時觸發(fā),并攜帶一個value參數(shù)表示被點擊按鈕的文本值;Evaluate表示計算意圖,即用戶點擊了等號按鈕時觸發(fā)。

3.4 定義MainActivity類

最后,我們需要定義MainActivity類,用于實現(xiàn)View和Intent之間的交互邏輯。MainActivity類繼承MainActivity類繼承自AppCompatActivity,并實現(xiàn)了一個名為MviView的接口。MviView接口定義了兩個方法:render和intents。render方法用于根據(jù)Model渲染界面;intents方法用于返回一個Observable對象,該對象發(fā)射用戶或系統(tǒng)產(chǎn)生的Intent。MainActivity類的內(nèi)容如下:

// MainActivity.kt

class MainActivity : AppCompatActivity(), MviView<Model, Intent> {

    private lateinit var tvResult: TextView // 結(jié)果文本視圖
    private lateinit var glButtons: GridLayout // 按鈕網(wǎng)格布局
    private val buttons = mutableListOf<Button>() // 按鈕列表
    private val calculator = Calculator() // 計算器對象,用于處理業(yè)務(wù)邏輯

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        tvResult = findViewById(R.id.tv_result) // 初始化結(jié)果文本視圖
        glButtons = findViewById(R.id.gl_buttons) // 初始化按鈕網(wǎng)格布局

        for (i in 0 until glButtons.childCount) { // 遍歷按鈕網(wǎng)格布局中的所有子視圖
            val view = glButtons.getChildAt(i) // 獲取當前子視圖
            if (view is Button) { // 如果子視圖是按鈕類型
                buttons.add(view) // 將按鈕添加到按鈕列表中
                view.setOnClickListener { // 設(shè)置按鈕點擊監(jiān)聽器
                    when (view.id) { // 根據(jù)按鈕id判斷點擊事件類型
                        R.id.btn_clear -> intents().onNext(Intent.Clear) // 如果是清除按鈕,發(fā)送清除意圖
                        R.id.btn_equal -> intents().onNext(Intent.Evaluate) // 如果是等號按鈕,發(fā)送計算意圖
                        else -> intents().onNext(Intent.Append(view.text.toString())) // 否則,發(fā)送追加意圖,并攜帶按鈕文本值作為參數(shù)
                    }
                }
            }
        }

        bind() // 調(diào)用bind方法,將View和Model進行綁定

    }

    override fun render(model: Model) { 
        tvResult.text = model.expression + model.result + model.error 
        if (model.error.isNotEmpty()) {
            Toast.makeText(this, model.error, Toast.LENGTH_SHORT).show()
        }
    }

    override fun intents(): PublishSubject<Intent> {
        return PublishSubject.create()
    }

    private fun bind() {
        intents()
            .scan(Model()) { state, intent ->
                calculator.process(state, intent)
            }
            .distinctUntilChanged()
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(this::render)
    }
}

這段代碼定義了一個名為MainActivity的類,并實現(xiàn)了MviView接口。在onCreate方法中,我們初始化了結(jié)果文本視圖、按鈕網(wǎng)格布局和計算器對象,并為每個按鈕設(shè)置了點擊監(jiān)聽器。在點擊監(jiān)聽器中,我們根據(jù)不同的事件類型,發(fā)送相應(yīng)的Intent到intents方法返回的Observable對象中。然后我們調(diào)用了bind方法,將View和Model進行綁定。

在bind方法中,我們使用scan操作符對intents發(fā)射的每個Intent進行處理,并更新Model狀態(tài)。然后我們使用distinctUntilChanged操作符過濾掉重復(fù)或相同的Model狀態(tài)。最后我們使用observeOn操作符切換到主線程,并訂閱render方法。

在render方法中,我們根據(jù)Model狀態(tài)更新結(jié)果文本視圖,并顯示錯誤信息(如果有)。

4. 優(yōu)缺點總結(jié)

模式 優(yōu)點 缺點 應(yīng)用場景 解決了哪些問題
MVC 簡單易懂,分工明確,有利于團隊協(xié)作和維護 View和Model之間的耦合度高,導(dǎo)致視圖層邏輯復(fù)雜,難以測試 適合簡單的界面展示和交互 實現(xiàn)關(guān)注點分離,提高代碼可讀性和復(fù)用性
MVP 解決了MVC中View和Model之間的耦合問題,降低了視圖層的邏輯復(fù)雜度,提高了可測試性 Presenter和View之間的接口過多,增加了代碼量和維護成本 適合復(fù)雜的界面展示和交互 實現(xiàn)視圖和數(shù)據(jù)的解耦,提高代碼可維護性和擴展性
MVVM 基于數(shù)據(jù)綁定技術(shù),進一步降低了View和ViewModel之間的耦合度,簡化了代碼量,提高了可維護性和可擴展性 數(shù)據(jù)綁定技術(shù)有一定的學(xué)習(xí)成本,可能存在內(nèi)存泄漏或性能問題 適合需要頻繁更新UI或者有復(fù)雜業(yè)務(wù)邏輯的界面 實現(xiàn)雙向數(shù)據(jù)綁定,提高代碼可讀性和用戶體驗
MVI 基于響應(yīng)式編程思想,將應(yīng)用程序看作一個狀態(tài)機器,將用戶輸入、網(wǎng)絡(luò)請求等事件統(tǒng)一處理,并生成新的狀態(tài)更新UI 需要掌握響應(yīng)式編程框架如RxJava等,狀態(tài)管理可能比較復(fù)雜或冗余 適合需要處理多種異步事件或者有嚴格要求的用戶體驗的界面 實現(xiàn)單向數(shù)據(jù)流動,提高代碼可預(yù)測性和穩(wěn)定性
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容