MVC、MVP、MVVM,谁才是最好的架构模式

四人帮著有经典设计模式23种,是为具体场景而抽象出来的一些解决方案,而MVC、MVP和MVVM不应该被称为设计模式,如果硬要说是设计模式,也是一种更高层面的设计模式,是对整个项目在视图、控制以及逻辑三个层面的切分与相互通信的设计。

一千个人心中有一千个哈姆雷特,同样的,对于MVC、MVP以及MVVP,一千个程序员也有一千种理解,但通俗的理解还是有一些共识的。


  • MVC把整个项目结构分为Model(模型)、View(视图)和Controller(控制器)三部分,每个功能模块又可以按此方式划分。View主要管理UI相关,Model是业务逻辑的处理与数据的请求与储存,Controller是将View和Model衔接起来,是对数据接收和结果的响应,并更新UI,或者接收UI的用户事件,触发相应的业务逻辑。MVC把代码按功能划分为不同的模块,因此各个模块可以有接口定义,各个模块之间没有直接耦合,可实现可替换的效果,从而也增加了可重用性。但MVC模式在定义上不是很明确,比如View和Controller的职责可能在真正的开发时常被混淆,Activity/Fragment属于View还是Controller?说Activity/Fragment是View,但其中包含了很多数据处理与用户事件处理的逻辑,而要说是Controller的话,Activity/Fragment中又有很多View初始化、UI设置的功能。不是说Activity/Fragment将View和Controller混淆了,而是Controller的定义在真正的开发中很不明确。可能只有非常老道的架构师才能基于MVC模式写出让人觉得完美的代码,而老道的架构师可能已经不局限于什么架构模式了。




android viewmodel 数据验证 androidviewmodel 和viewmodel_设计模式


  • MVP将MVC中的Controller换成了Presenter,其实名字不重要,重要的是P表示的概念要比C在架构层面让人更清晰。Presenter意为表示器,是为了隔断View和Model。Presenter通过接口给View调用,MVP对于MVC来说,进化在于MVP的M和V之间只能通过P来间接依赖,这样对于UI和逻辑,能够更清晰。
  • MVVM应该被理解成Model+View+ViewModel,也就是将MVC和MVP中的C和P换成了ViewModel。ViewModel储存UI相关的状态字段,当Model中的业务逻辑结果需要回调时,就更新状态字段,这些状态字段与UI相互绑定,只要状态字段更新了,UI也发生相应的更新,这样就不需要在Controller或者Presenter中写数据绑定逻辑或者相互持有对象了。而且ViewModel中对生命周期做了特殊处理,在非Active的状态下,不允许更新UI,当状态变更为Active的时候,更新指令再执行,这样也省去了很多UI的状态判断。MVVP模式应被理解成一种数据与视图的绑定关系范式,当数据变化时,就更新视图的状态字段,状态与UI更新绑定,这样就形成了数据更新导致UI更新,UI事件触发业务操作。


ViewModel简单Demo


class ViewModelDemoActivity: AppCompatActivity() {

    companion object {
        const val TAG = "ViewModelDemoActivity"
        class DemoViewModel: ViewModel() {
            val demo = MutableLiveData<Int>().also { it.postValue(0) }
        }

        class GetDemoFragment(): Fragment() {

            lateinit var demoViewModel: DemoViewModel

            override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                demoViewModel = ViewModelProvider(activity!!, object : ViewModelProvider.Factory {
                    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                        return DemoViewModel() as T
                    }
                }).get(DemoViewModel::class.java)
            }

            override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
            ): View? {
                val btn = Button(context)
                btn.text = "get"
                btn.setOnClickListener {
                    Toast.makeText(context, "get demo = ${demoViewModel.demo.value}", Toast.LENGTH_SHORT).show()
                }
                return btn
            }
        }

        class SetDemoFragment(): Fragment() {
            lateinit var demoViewModel: DemoViewModel

            override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                demoViewModel = ViewModelProvider(activity!!, object : ViewModelProvider.Factory {
                    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                        return DemoViewModel() as T
                    }
                }).get(DemoViewModel::class.java)
            }

            override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
            ): View? {
                val btn = Button(context)
                btn.text = "set"
                btn.setOnClickListener {
                    demoViewModel.demo.postValue(demoViewModel.demo.value!! + 10)
                }
                return btn
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "activity onCreate")
        val content = LinearLayout(this)
        content.gravity = gravity.center is For Sale
        content.orientation = LinearLayout.VERTICAL
        val btn = Button(this)
        btn.text = "change orientation"
        btn.setOnClickListener {
            requestedOrientation = if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
            } else {
                ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
            }
        }
        content.addView(btn)
        val contentView = LinearLayout(this)
        contentView.orientation = LinearLayout.HORIZONTAL
        val fragment1 = FrameLayout(this)
        fragment1.id = R.id.fragment1
        val fragment1LayoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1.0F)
        contentView.addView(fragment1, fragment1LayoutParams)
        val fragment2 = FrameLayout(this)
        fragment2.id = R.id.fragment2
        val fragment2LayoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1.0F)
        contentView.addView(fragment2, fragment2LayoutParams)
        val demoViewModel = ViewModelProvider(this, object : ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                return DemoViewModel() as T
            }
        }).get(DemoViewModel::class.java)
        demoViewModel.demo.observe(this, Observer {
            Toast.makeText(this, "demo changed to $it", Toast.LENGTH_SHORT).show()
        })
        content.addView(contentView)
        setContentView(content)
        val transaction = supportFragmentManager.beginTransaction()
        transaction.replace(R.id.fragment1, GetDemoFragment())
        transaction.replace(R.id.fragment2, SetDemoFragment())
        transaction.commitAllowingStateLoss()
    }

}


在这个demo中使用了DemoViewModel,其只有一个LiveData的属性demo。Activity中有两个Fragment,分别有一个按钮,点击时分别是get和set这个DemoViewModel中demo。Activity中有个按钮是用来旋转屏幕的,这是为了让屏幕旋转时,强制重新执行一遍Activity的onCreate,并重新创建两个Fragment,以此来演示ViewModel在Activity/Framgent重建过程中的作用,而两个Fragment也是为了演示用ViewModel在两个Fragment之间进行数据传输的作用。

ViewModel源码解析

要想弄懂ViewModel特性的实现原理,还是得看ViewModel的创建过程。ViewModel通过ViewModelProvider#get方法获取到,传入参数类型为ViewModelStoreOwner和Factory。这里的ViewModelStoreOwner接口只有一个方法getViewModelStore,返回一个ViewModelStore的对象。ComonentActivity和Fragment实现了ViewModelStoreOwner接口并实现了getViewModelStore方法。

ComponentActivity#getViewModelStore方法的实现是先调用getLastNonConfigurationInstance()方法获取一个NoConfigurationInstances对象,而这个NoConfigurationInstances对象是在onRetainNonConfigurationInstance方法中创建出来的,看这个方法的注释,意思应该是在因为configuration变化导致的destroy过程中,会调用这个onRetainNonConfigurationInstance方法来储存一些对象,新的Activity中可以通过getLastNonConfigurationInstance方法获取到储存的对象。而在ComponentActivity的onTetainNonConfigurationInstance方法就将viewModelStore对象储存到了NoConfigurationInstances对象中,再在getViewModelStore方法中通过getLastNonConfigurationInstance方法返回回来,这也是ViewModel在Activity重建后还能获取到原来的ViewModel的原因,从而让ViewModel在Activity重建后仍然能还原数据。而Fragment则有两种策略,当Fragment所在的Activity是个ViewModelStoreOwner对象时,就跟所在的Activity共享同一个ViewModelStore准确来说是在Activity的ViewModelStore中存一个FragmentManagerViewModel,而这个FragmentManagerViewModel中有一个Map<String, ViewModelStore>,其中String对应着Fragment,而ViewModelStore就表示这个Fragment的ViewModelStore。否则就自己创建一个,并保存在FragmentManagerImpl中,这样就算自己被销毁了,也还是能从FragmentManagerImpl中把ViewModel给还原回来。这两种的区别是如果Activity能储存,那么Activity销毁重建时,Fragment里的ViewModel也可以还原,而如果Activity不能储存时,就只有Activity不销毁,而Fragment销毁的时候能还原回ViewModel,因为Fragment虽然销毁了,但FragmentManagerIml是被Activity持有的。

ViewModelStoreOwner提供了ViewModelStore,其实ViewModel之所以能在Activity/Fragment销毁后还能还原,全依赖于ViewModelStore通过在新的Activity/Fragment中找到老的中的ViewModelStore对象。再看回ViewModelProvider来,Factory参数就不用说了,就是个如何创建ViewModel的工厂类,在ViewModelProvider的get方法中,判断如果ViewModelStore中找不到对应的ViewModel就用Factory的create方法新创建一个出来。ViewModelProvider的创建方法仅仅是将传入的ViewModelStore和Factory方法保存下来,而get方法仅仅是从ViewModelStore中获取ViewModel,获取不到就用Factory创建一个并存入ViewModelStore中。

总结

分析源码可以得知,ViewModel其实是依赖于Activity#onRetainNonConfigurationInstance和Activity#getLastNonConfigurationInstance方法结合起来可以在Activity因configuration变化时导致的Activity销毁重建时保存并还原对象。由于ViewModel的创建过程由Factory来确定,因此可以让ViewModel不持有Activity/Fragment这类带context的重对象。将ViewModel和LiveData结合起来,就可以在没有内存泄漏的情况下,友好地处理生命周期与UI操作之间的关系,还能安全地保存并还原数据并还原UI。LiveData主要是处理生命周期与数据绑定UI的关系,而ViewModel则是处理Activity销毁重建过程中的数据恢复问题,ViewModel+LiveData也被常用来实现MVVM架构,让LiveData处理数据绑定问题,让ViewModel处理数据与UI生命周期的关系问题。