常见客户端框架(MVC、MVP和MVVM)
一、MVC
MVC全名为Model-View-Controller,图解如下:
- View:负责与用户交汇,显示界面(即UI)。
- Controller:负责接收来自view的请求,处理业务逻辑。
- Model:负责数据逻辑,网络请求数据以及本地数据库操作数据等。
可以看到:MVC的本质就是按照UI、业务、数据不同的职责分三大模块,彼此分工。在MVC架构中,Controller是业务的主要承载者,几乎所有的业务逻辑都在Controller中进行编写。View主要负责UI逻辑,而Model是数据逻辑。
1.1 优缺点
但是一般的开发中,MVC的架构也存在不足:
- 几乎所有的业务逻辑代码都在controller中进行,会导致非常臃肿,降低项目的可测试性与可维护性。
- view直接持有controller和model实例,不同职责的代码进行耦合,导致代码耦合性高,模块分工不清晰。
MVC好处:简单。他不需要写很多的代码来让代码解耦,这在在初创公司的小型项目非常有用。小型项目总体的代码量级小,可以提高开发效率
1.2 例子
应用MVC框架完成计数器的设计
1.2.1 Model(模型):数据存储和处理
class CounterModel {
var count: Int = 0
fun increment() {
count++
}
fun decrement() {
if (count > 0) count--
}
fun reset() {
count = 0
}
}
1.2.2 View(视图):UI
视图部分将是一个简单的界面,在实际应用中,它可能是一个 Android Activity 或 Fragment。
// 伪代码视图
class CounterView {
// 假设这是 Android 的 TextView
fun updateCount(count: Int) {
// 在这里更新界面元素以显示新的计数
textView.setText($count)
}
}
1.2.3 Controller(控制器):处理用户页面交互的逻辑
可以看到Controller需要持有model和view的实例,造成模块间不解耦.
class CounterController {
private val model = CounterModel()
private val view = CounterView()
// 用户点击增加按钮
fun onIncrementButtonClicked() {
model.increment()
updateView()
}
// 用户点击减少按钮
fun onDecrementButtonClicked() {
model.decrement()
updateView()
}
// 用户点击重置按钮
fun onResetButtonClicked() {
model.reset()
updateView()
}
// 更新视图以反映模型的当前状态
private fun updateView() {
view.updateCount(model.count)
}
}
1.2.4 主程序
在主程序中,我们创建控制器实例,并模拟用户操作来测试 MVC 架构。
fun main() {
val controller = CounterController()
// 模拟用户点击增加按钮
controller.onIncrementButtonClicked()
// 模拟用户点击减少按钮
controller.onDecrementButtonClicked()
// 再次模拟用户点击增加按钮
controller.onIncrementButtonClicked()
// 模拟用户点击重置按钮
controller.onResetButtonClicked()
}
二、MVP
MVP全名是Model-View-Presenter。图解如下:
- View:UI模块,负责界面显示和与用户交汇。
- Presenter:负责业务逻辑,起着连接View和Model桥梁的作用。
- Model:专注于数据逻辑。
MVP和MVC的区别很明显就在这个Presenter中。为了解决MVC中代码的耦合严重性,把业务逻辑都抽离到了Presenter中。这样View和Model完全被隔离,实现了单向依赖,大大减少了耦合度。view和prensenter之间通过接口来通信。
不同的view可以通过实现相同的接口来共享prensenter。presenter也可以通过实现接口来实现动态更换逻辑。Model是完全独立开发的,向外暴露的方法参数中含有callBack参数,可以直接调用callBack进行回调。
**MVP的最大特点就是接口通信,接口的作用是为了实现模块间的独立开发。**presenter的作用就是接受view的请求,然后再在model中获取数据后调用view的方法进行展示,因为每个界面都是不同的,这就导致了每个Activity/Fragment都必须写一个IView接口,然后还需要再写个IPresenter接口,从而产生了非常多的接口,需要编写大量的代码来进行解耦。 其次,prensenter并没有真正解耦,他还需要调用view的接口进行UI操作,解耦没有彻底。
2.1 优缺点
优点:
- MVP通过模块职责分工,抽离业务逻辑,降低代码的耦合性
- 实现模块间的单向依赖,代码思路清晰,提高可维护性
- 模块间通过接口进行通信,降低了模块间的耦合度,可以实现不同模块独立开发或动态更换
缺点:
- 过度设计导致接口过多造成了接口地狱的问题,编写大量的代码来实现模块解耦,降低了开发效率
- 并没有彻底进行解耦,prensenter需要同时处理UI逻辑和业务逻辑,这就导致presenter越来越臃肿
2.2 例子
应用MVP框架完成计数器的设计,MVP框架通常用于Android开发中,以解耦视图和逻辑代码。
2.2.1 Model(模型)
// CounterModel.kt
class CounterModel {
var count: Int = 0
fun increment() {
count++
}
fun decrement() {
if (count > 0) count--
}
fun reset() {
count = 0
}
}
2.2.2 View(视图接口)
// CounterView.kt
interface CounterView {
fun showCount(count: Int)
fun showError() // 如果有需要,可以添加错误处理的方法
}
2.2.3 Presenter(展示器)
// CounterPresenter.kt
class CounterPresenter(private val view: CounterView, private val model: CounterModel) {
fun increment() {
model.increment()
view.showCount(model.count)
}
fun decrement() {
model.decrement()
view.showCount(model.count)
}
fun reset() {
model.reset()
view.showCount(model.count)
}
}
2.2.4 Activity 或 Fragment(作为视图的具体实现)
// CounterActivity.kt
class CounterActivity : AppCompatActivity(), CounterView {
private lateinit var presenter: CounterPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_counter) // 假设有一个对应的布局文件
val model = CounterModel()
presenter = CounterPresenter(this, model)
// 假设这里有两个按钮,一个用于增加,一个用于减少
// findViewById并设置OnClickListener
// 增加按钮的点击事件
findViewById<Button>(R.id.increment_button).setOnClickListener {
presenter.increment()
}
// 减少按钮的点击事件
findViewById<Button>(R.id.decrement_button).setOnClickListener {
presenter.decrement()
}
// 重置按钮的点击事件(如果需要的话)
// findViewById<Button>(R.id.reset_button).setOnClickListener {
// presenter.reset()
// }
// 初始显示计数
view.showCount(model.count)
}
override fun showCount(count: Int) {
// 更新UI以显示计数
findViewById<TextView>(R.id.count_textview).text = "Count: $count"
}
override fun showError() {
// 处理错误情况(如果需要的话)
}
}
在这个例子中,CounterActivity 实现了 CounterView 接口,作为视图的具体实现。它持有一个 CounterPresenter 的实例,并通过这个实例与模型进行交互。当用户点击按钮时,CounterActivity 会调用 CounterPresenter 的相应方法,CounterPresenter 会更新模型并通知视图进行更新。视图通过实现 showCount 方法来更新界面上的计数显示。
三、MVVM
MVVM和上面两种架构模式一样都是一种架构思想,只是谷歌推出了jetpack架构组件来让我们更好的使用这种架构模式。
MVVM,全名为Model-View-ViewModel。图解:
- View:和前面的MVP、MVC中的View一样,负责UI界面的显示以及与用户的交汇。
- Model:同样是负责网络数据获取或者本地数据库数据获取。
- ViewModel:负责存储view的数据映像以及业务逻辑。
MVVM的view和model和前面的两种架构模式是差不多的,重点在ViewModel。viewModel通过将数据和view进行绑定,修改数据会直接反映到view上,通过数据驱动型思想,彻底把MVP中的Presenter的UI操作逻辑给去掉了。而viewModel是绑定于单独的view的,也就不需要进行编写接口了。但viewModel中依旧有很多的业务逻辑,但是因为把view和数据进行绑定,这样可以让view和业务彻底的解耦了。view可以专注于UI操作,而viewModel可以专注于业务操作。因而:MVVM通过数据驱动型思想,彻底把业务和UI逻辑进行解耦,各模块分工职责明确。
3.1 优缺点
- 优点:View只需要关注Viewmodel的数据部分,而无需知道数据是怎么来的;而ViewModel只需要关注数据逻辑,而不需要知道UI是如何实现的。View可以随意更换UI实现,但ViewModel却完全不需要改变。
- 缺点:MVVM的ViewModel模块依旧很臃肿。而且MVVM需要学习数据绑定框架,具有一定的上手难度。
3.2 MVVM+Jetpack框架
为了解决上面两个问题,google推出了上手难度相对较低的mvvm+Jetpack框架
上图解析如下:
- View对应的就是Activity和Fragment,在这里进行UI操作。
- ViewModel中包含了LiveData,这是一种可观察数据类型框架。View通过向LIveData注册观察者,当LiveData发生改变时,就会直接调用观察者的逻辑把数据更新到view上。
- ViewModel完全不需要关心UI操作,只需要专注于数据与业务操作。
- Repository代表了Model层,Repository对ViewModel进行了减压,把业务操作般到了Repository中,避免了viewModel臃肿。
- Repository对请求进行判断是要到本地数据库获取还是网络请求获取分别调用不同的模块
jetpack的架构组件库是一整套完整的架构组件库,包括了:DataBinding,LiveData,ViewModel,Navigation,Lifecycle。下面我们简单了解一下每个组件的功能: 访问基于 activity 构建的可组合 API。
组件名 | 功能点 |
---|---|
DataBinding | 1.解基于数据驱动思想,决视图调用一致性问题,实现双向绑定2.避免编写样板式代码,提高效率 |
LiveData | 1.通过唯一可信源获取数据,正确分发数据 2.与Lifecycle结合,拥有生命周期感知能力,配合viewModel实现作用域可控 3.实现模块的单向依赖,抛弃接口回调 |
ViewModel | 1. 托管界面状态,解决状态管理问题 2. 实现跨页面数据分享,并为数据设置作用域,做到作用域可控 3. 实现单向依赖,避免内存泄露 |
Lifecycle | 1.以简便地方式解决生命周期管理的一致性问题 2.避免内存泄露的情况下让第三方组件随时获取生命周期状态,追踪事故所在的生命周期源,对错过时机的异步操作及时停止 |
Navigation | 通过遵循导航定则实现对Fragment的管理 |
我们只需要遵循他的开发规范,使用他的架构框架,就可以开发出非常健壮的项目。MVVM的本质是什么?
一种基于数据驱动型,将UI逻辑和业务逻辑彻底分离的架构模式。
3.3 例子
3.3.1 Model(模型)
// CounterModel.kt
class CounterModel {
var count: Int = 0
fun increment() {
count++
}
fun decrement() {
if (count > 0) count--
}
fun reset() {
count = 0
}
}
3.3.2 ViewModel(视图模型)
// CounterViewModel.kt
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class CounterViewModel : ViewModel() {
private val counterModel = CounterModel()
val count: MutableLiveData<Int> = MutableLiveData(counterModel.count)
fun increment() {
counterModel.increment()
count.value = counterModel.count
}
fun decrement() {
counterModel.decrement()
count.value = counterModel.count
}
fun reset() {
counterModel.reset()
count.value = counterModel.count
}
}
3.3.3 View(视图,Activity或Fragment)
// CounterActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
class CounterActivity : AppCompatActivity() {
private lateinit var viewModel: CounterViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_counter)
// 获取ViewModel实例
viewModel = ViewModelProvider(this).get(CounterViewModel::class.java)
// 绑定视图元素
val incrementButton = findViewById<Button>(R.id.increment_button)
val decrementButton = findViewById<Button>(R.id.decrement_button)
val countTextView = findViewById<TextView>(R.id.count_textview)
// 观察ViewModel中的LiveData
viewModel.count.observe(this, Observer { newCount ->
countTextView.text = "Count: $newCount"
})
// 设置按钮点击事件
incrementButton.setOnClickListener {
viewModel.increment()
}
decrementButton.setOnClickListener {
viewModel.decrement()
}
// 如果需要重置按钮,可以添加类似的逻辑
}
}
3.3.4 布局文件(activity_counter.xml)
<!-- activity_counter.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/count_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="Count: 0" />
<Button
android:id="@+id/increment_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Increment" />
<Button
android:id="@+id/decrement_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Decrement" />
<!-- 如果需要重置按钮,可以添加类似的Button -->
</LinearLayout>
在这个MVVM示例中,CounterViewModel 持有 CounterModel 的实例,并暴露了一个 MutableLiveData 类型的 count 属性,用于在视图和模型之间传递数据。CounterActivity 作为视图,通过 ViewModelProvider 获取 CounterViewModel 的实例,并观察 count 的变化来更新界面。按钮的点击事件会调用 CounterViewModel 中的方法来更新模型,模型的变化会通过 LiveData 传递给视图进行显示。
总结
MVC是不同职责代码分离,MVP是在MVC的基础上通过接口通信降低模块间的耦合性,MVC,MVP都是广义上的架构模式,Android只是他们的一个应用场景。MVVM是专注于页面开发的架构模式,更加契合页面开发模式。