Android 架构模式 - MVC,MVP 和 MVVM

196 阅读5分钟

MVC

MVC 是指模型(model)- 视图(View)- 控制器(controller)

  • Model:负责数据的加载和存储。
  • View:负责界面数据的显示,与用户进行交互。
  • Controller:负责逻辑业务的处理。

View 接受用户的请求,然后将请求传递给 Controller,Controller 进行业务逻辑处理后,通知 Model 去更新,Model 数据更新后,通知 View 去更新界面显示。在 Android 项目中,一般由 Activity 充当 Controller,XML 文件作为 View 层,Model 层即为数据结构和相关的类。

截图_选择区域_20221213105833.png

这里统一使用 wanandroid 开放 api

interface NetApi {
    @GET("/hotkey/json")
    suspend fun getHotKey(): Response?

    companion object {
        private const val BASE_URL = "https://www.wanandroid.com/"
        fun createApi(): NetApi =
            Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create())
                .build().create(NetApi::class.java)
    }
}

data class HotWords(
    val id: String,
    val name: String,
)

data class Response(
    val errorCode: Int,
    val errorMsg: String,
    val data: List<HotWords>
)

全篇需要用到的依赖有

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation "androidx.activity:activity-ktx:1.5.1"
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

创建 Model 层,请求数据。

class HttpModel {

    suspend fun getData() =
        try {
            val result = NetApi.createApi().getHotKey()
            if (result != null && !result.data.isNullOrEmpty()) {
                result.data.toString()
            } else {
                NO_DATA
            }
        } catch (e: Exception) {
            e.toString()
        }
}

View 层很简单,就一个 TextView,用来显示返回的结果。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/result_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

Controller 层通常为 Activity 或 Fragment,需要在其中进行逻辑处理。

class MainActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initView()
    }

    private fun initView() {
        val resultText = findViewById<TextView>(R.id.result_text)
        lifecycleScope.launch {
            val httpText = HttpModel().getData()
            resultText.text = httpText
        }
    }
    
}

MVC 模式的优点就是简单方便,但是它的缺点也比较明显,随着界面的增多和逻辑复杂度的提高,Activity 会显得十分臃肿,维护起来比较困难。

MVP

MVP,全称 Model - View - Presenter

  • View:对应于 Activity,负责 View 的绘制和用户交互。
  • Model:实体模型。
  • Presenter:负责完成 View 和 Model 之间的交互。

MVC 中 V 对应的是布局文件,MVP 中 V 对应的是 Activity

使用 Presenter 作为 View 与 Model 之间的桥梁,当 View 层需要展示数据时,首先会调用 Presenter 的引用,然后 Presenter 层会调用 Model 层请求数据,当 Model 层数据加载成功之后,Presenter 层再调用 View 层的接口将数据展示给用户。

截图_选择区域_20221213112448.png

创建 MainContracts,集中管理 View 和 Presenter 。

class MainContracts {
    interface IView {
        // View 层获取数据回调方法
        fun onResultData(data: String)
        fun onResultFail(exp: String)
    }

    interface IPresenter {
        // View 层向 Presenter发送请求方法
        suspend fun requestData()
    }
}

Presenter 层,在此进行网络请求,可以弱化 Model 的作用。

class MainPresenter(private var iView: MainContracts.IView) : MainContracts.IPresenter {

    override suspend fun requestData() {
        try {
            val result = NetApi.createApi().getHotKey()
            if (result != null && !result.data.isNullOrEmpty()) {
                iView.onResultData(result.data.toString())
            } else {
                iView.onResultData(NO_DATA)
            }
        } catch (e: Exception) {
            iView.onResultFail(e.toString())
        }
    }

}

View 层,通过接口获取数据。

class MainActivity : AppCompatActivity(), MainContracts.IView {

    private lateinit var resultText: TextView
    private val mainPresenter: MainPresenter by lazy {
        MainPresenter(this)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        resultText = findViewById(R.id.result_text)
        lifecycleScope.launch {
            mainPresenter.requestData()
        }
    }

    override fun onResultData(data: String) {
        resultText.text = data
    }

    override fun onResultFail(exp: String) {
        resultText.text = exp
    }

}

MVP 通过 Presenter 实现数据和视图之间的交互,将复杂的逻辑代码提取到 Presenter 中进行处理,简化了 Activity 的职责,同时避免了 View 和 Model 的直接联系,又通过 Presenter 实现两者之间的沟通,降低了耦合度。不过,它也存在着缺点,随着项目复杂度的提升,Activity 或 Fragment 会不断增加,Presenter 层对应的接口和实现类会爆炸式地增长,Presenter 层就会越来越臃肿。

MVVM

  • Model:数据层,包含数据实体和对数据实体的操作。
  • View:界面层,对应于 Activity,XML,View,负责数据显示以及用户交互。
  • ViewModel:关联层,将 Model 和 View 进行绑定,Model 或 View 更改时,实时刷新对方。

mvvm.png

View 层接收用户操作,并通过持有的 ViewModel 去处理业务逻辑,请求数据,ViewModel 层通过 Model 去获取数据,然后 Model 又将最新的数据传回 ViewModel 层,到这里,ViewModel 与 Presenter 所做的事几乎是一样的。但是 ViewModel 不会持有 View 层的引用,而是 View 层会通过观察者模式监听 ViewModel 层的数据变化,当有数据更新时,View 层能自动收到新的数据并刷新界面。

打开 dataBinding

android {
    ...
    buildFeatures {
        dataBinding true
    }
}  

界面中使用 dataBinding 进行数据绑定

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="result"
            type="String" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{result}" />

    </LinearLayout>
    
</layout>

ViewModel 层,进行网络数据请求。

class MainViewModel : ViewModel() {

    private val resultLiveData = MutableLiveData<String>()

    fun getData() {
        viewModelScope.launch {
            try {
                val result = NetApi.createApi().getHotKey()
                if (result != null && !result.data.isNullOrEmpty()) {
                    resultLiveData.value = result.data.toString()
                } else {
                    resultLiveData.value = NO_DATA
                }
            } catch (e: Exception) {
                resultLiveData.value = e.toString()
            }
        }
    }

    fun getResultLiveData() = resultLiveData
    
}

Activity 中监听 ViewModel 层的数据变化

class MainActivity : AppCompatActivity() {

    private val mainViewModel by viewModels<MainViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        mainViewModel.getData()
        mainViewModel.getResultLiveData().observe(this) {
            binding.result = it
        }
    }

}

MVVM 解耦得更彻底,在 MVP 模式中,Presenter 需要持有 View 的引用,才能去刷新 UI,在 MVVM 模式中,View 和 Model 使用 DataBinding 进行双向绑定,一方改变会直接通知另一方,使得 ViewModel 能专注于业务逻辑的处理,而无需关心 UI 的刷新。MVVM 不会像 MVC 一样导致 Activity 中代码量巨大,也不会像 MVP 一样出现大量的 View 接口。

但是,MVVM 也有些缺点:

  • 调试难度增加:DataBinding 通过 XML 绑定数据,当 UI 展示异常时,问题可能出在数据逻辑,绑定表达式或生命周期管理中,定位问题比直接在代码中操作 UI 更间接。
  • 状态管理复杂度:当页面状态复杂,ViewModel 需要维护大量的状态变量。若缺乏统一的状态管理策略,可能导致状态逻辑混乱,难以维护。
  • 过度设计与代码冗余:对于简单场景(如一个仅展示静态内容的页面),MVVM 的分层设计会显得冗余,增加不必要的类和接口,反而不如直接使用传统模式高效。