MVVM的学习记录和思考

栏目: IOS · Android · 发布时间: 7年前

内容简介:公司的项目,一直是以Activity为主体,类MVC模式的进行开发的,加入现在的公司一年以来,因为现在接手项目比较老,因此之前一直是在做项目新的开发加填以前的老坑。项目的主页甚至还在用已经废弃了很久的TabActivity,之前下定决心改把主页修改成了Activity+多Fragment的模式,以前的界面,Activity的逻辑过于复杂,正好接着这次重构的机会也和同事了解学习一下MVVM,为之后的开发算是做一个自己的准备吧。Google为了MVVM,提供了不少的:chestnut:以及框架,现在Google

公司的项目,一直是以Activity为主体,类MVC模式的进行开发的,加入现在的公司一年以来,因为现在接手项目比较老,因此之前一直是在做项目新的开发加填以前的老坑。项目的主页甚至还在用已经废弃了很久的TabActivity,之前下定决心改把主页修改成了Activity+多Fragment的模式,以前的界面,Activity的逻辑过于复杂,正好接着这次重构的机会也和同事了解学习一下MVVM,为之后的开发算是做一个自己的准备吧。

学习的过程

DataBinding

Google为了MVVM,提供了不少的:chestnut:以及框架,现在Google主推的就是Jetpack了,MVVM的核心就是数据绑定,这个在Android里,因为XML作为view的功能极其孱弱,Google在Jetpack里提供了Databinding的组件,让XML和ViewModel进行数据绑定,通过绑定,如果ViewModel的数据变化,UI即可出现对应的响应。

XML内使用DataBinding

<variable
        name="viewmodel"
        type="com.acclex.ViewModel" />
    <TextView
        android:text="@{viewmodel.user.name}"
复制代码

LiveData

为了让ViewModel去操作数据,方便Activity和XML观察数据的改变,Google还提供了LiveData这个框架,它的本质是一个类似RxJava的实现观察者模式的框架,大致使用方式如下

ViewModel内

private val _user: MutableLiveData<User> 
    val user:LiveData<User>
        get() = _user

    // 更改数据
    private fun updateUser() {
        _user.value = User() 
    }
复制代码

Activity或者Fragment内

viewModel.user.observe(this, Observer<User> { user ->
        user?.let {
            //todo do something
        }
    })
复制代码

很简单有效就可以实现观察者模式,让view层和ViewModel层解耦,通过这种方式去处理数据的变动,和RxJava实现的功能是一致的,因此MVVM也可以使用RxJava实现一样的功能。通过观察者模式,可以让view与数据解耦开来,Activity以及Fragment不需要再去处理任何与数据相关的事情。

LiveData还是有一些好处的,因为它是Google开发封装的,它自带了生命周期的管理,因为它observe的直接是LifecycleOwner这个对象,如果LifecycleOwner的对象被销毁,LiveData则会自己去clean掉,个人认为和生命周期绑定,这是一个很棒的优点,更多的优点,Google的官方文档有详细的介绍: developer.android.google.cn/topic/libra…

ViewModel

Jetpack内还提供了ViewModel让开发者去使用,ViewModel其实就是对业务逻辑和业务数据的操作,在ViewModel里,不会也不可以持有任何View的引用,定义了一个ViewModel后,我们通常在View层使用 val viewModel = ViewModelProviders.of(this).get(ViewModel::class.java) 这样的代码去获取ViewModel的实例,这个this可以是Activity或者Fragment,ViewModel被初始化后会一直保留在内存内,直到它所作用域也就是Fragment触发detached或者Activity触发finishes,它才会被回收。 如果我们需要在初始化ViewModel的时候传入构造参数,那么我们必须要写一个继承自 ViewModelProvider.NewInstanceFactory 的类,代码如下

class SampleViewModelFactory(
        private val model: Model
) : ViewModelProvider.NewInstanceFactory() {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>) =
            with(modelClass) {
                when {
                    isAssignableFrom(SampleViewModel::class.java) ->
                        SampleViewModel(model)
                    else ->
                        throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
                }
            } as T

}
复制代码

这些都是为了方便开发者使用MVVM模式,Google在Jetpack内提供给我们的一些组件,单独来看,这些组件的使用方法,学习成本并不高,同时并没有涉及到Model层单独做一个组件封装,因为Model可以说是最自由,也是定制最多的组件。之前在我写MVVM的demo的时候,我并没有单独的写出一个Model,甚至将获取数据写在了ViewMode里,让ViewModel去获取解析数据,并处理数据,之后的继续学习,特别是阅读了Google的android-architecture的源代码之后,之前的思路可以说是完全错误的,接下来我们就来谈谈关于MVVM内Model层的定义与使用

Model

不管是MVC,MVP,MVVM的 设计模式 内,均存在Model层,可见Model层是极其重要的。但是在Android开发内,Model层反而是可能存在感最薄弱的一层,因为现在获取数据的代码,不管是联网获取,或者是读取数据库,或者是读取本地的数据,代码已经精简到短短几行就可以实现,很多开发的时候,不自觉的把这些方式写在了Activity、Fragment内,又或者是写在了ViewModel或者是Presenter内,之前在读一个MVVM实现的时候,就直接将获取数据写在了ViewModel内。

那么这样写,会导致什么问题?如果是简单的数据以及相对简单的逻辑,它并不会造成太多的影响,可读性也没有收到很多的影响,但是如果需要进行单元测试的话,数据耦合在了逻辑里,会对单元测试造成极大的影响。这是我对这个问题的看法。(ps:小弟技术菜,没有想到别的一些问题,只能看出这一点影响可能较大的问题,有补充欢迎评论留言,谢谢!)

按照规范标准来看,Model层是负责数据存储,数据处理,以及获取数据。但是Google不像ViewModel、LiveData、DataBinding提供了现成的规范以及标准,因此我对Model层其实是有一些问题的

Model层如何构造,包含那些接口以及基本方法

这可以说是Model层最关键的问题了,因为这关系到Model层的实现。这点我觉得还是需要参照代码来说明的。

刚好在这次项目的新的开发任务,我部分模块才用了MVVM去实现,并且尝试了一下自己进行Model的设计,因此直接上代码,说一下我的理解。

interface BaseModel {
    interface ModelDataCallBack<T> {
        /**
         * 成功的回调函数
         * @param result 成功返回需要的类型
         */
        fun onSuccess(result: T)

        /**
         * 失败的回调函数
         * @param errorLog 失败后传递回去的错误的数据
         */
        fun onFailure(errorLog: String)
    }
}
复制代码

一个很简单的基础的Model,接口ModelDataCallBack负责回调结果给ViewModel处理结果,因为每个ViewModel、Model需要的数据不一样,因此回传的结果是由初始化传进来的泛型决定的。失败的话,在我设想里,应该是返回一个解析的结果或者异常log,也许是弹出一个Toast或者是一些别的逻辑,因此返回失败的结果定义成了返回一个String。 因此ViewModel、Model具体实现的代码大致如下

Model内

class SampleModel : BaseModel {
    fun getData(callBack: BaseModel.ModelDataCallBack<List<User>>){
        // 如果成功
        callBack.onSuccess(listOf(User("A",15)))
        // 失败
        callBack.onFailure("数据获取失败")
    }
}
复制代码

ViewModel内

class SampleViewModel(private val model:SampleModel) : ViewModel {

    private val _list = MutableLiveData<List<User>>()
    val list: LiveData<List<User>>
        get() = _list
    
    private fun updateUser() {
        model.getData(object :BaseModel.ModelDataCallBack<List<User>>{
            override fun onSuccess(result: List<User>) {
                list.value = result
            }

            override fun onFailure(errorLog: String) {
                TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
            }
        })
    }
}
复制代码

如上代码,可以达成ViewModel负责处理逻辑,而Model负责获取数据,ViewModel内接到成功或者失败的回调,可以触发LiveData的数据更新,View层可以通过观察LiveData内的数据,做到UI的更新或者变换。 这是我设想的一个简单的Model层,如果在开发中,一个Model内有许多的获取数据的方法或者接口,那么会产生大量的接口回调,如果是用kotlin的话,可以使用高阶函数直接传入成功或者失败的回调,类似如下代码

fun getData(success:(List<User>) -> Unit,
                fail:(String) ->Unit){
        success.invoke(listOf(User("A",15)))
        fail.invoke("数据获取失败")
    }
复制代码

在我的想法里,大量的接口回调基本是不可能避免的,如果有dalao有更好的方案,欢迎评论区提出,感谢!

上述代码,只是一个很简单的Model设计,如果在开发中使用这样的Model,会碰到的问题还有如下:

  • 单元测试如何实现
  • BaseModel这个接口是否有存在的意义,是否只需要一个ModelDataCallBack接口即可
  • 如果有数据缓存需求,应该怎么处理

这些问题都是这个简单的Model会碰到的,单元测试坑比较深,之后有空再单独写。 这个model的并不存在公共的实现方法,那么根本不需要一个单独的BaseModel接口,BaseModel的意义并不存在。如果这个model需要缓存,如果只是model内存储一个数据,那么这样的逻辑必然会影响到单元测试。因此这只是我的一个简单的想法,后续还要完善。

Google MVVM Sample

在自己的这些想法之后,我专门去学习了一下Google的Sample的源代码。放上Google的Sample,这里是链接: github.com/googlesampl… 。 先引用一张来自朋友博客的图片,博客链接:博客链接

MVVM的学习记录和思考

每个Model是一个Respository都是一个DataSource接口的实例,里面可能包含一种或者多种数据,每个数据类型都实现了DataSource接口。在Google的Sample里,也是根据这种模式去实现的。这是很理想化的Model设计,本地的数据,缓存的数据,测试的数据,单独区分,每个负责对应的职责,将代码解耦开来,是非常好的。 下面上代码

interface TasksDataSource {

    interface LoadTasksCallback {

        fun onTasksLoaded(tasks: List<Task>)

        fun onDataNotAvailable()
    }

    interface GetTaskCallback {

        fun onTaskLoaded(task: Task)

        fun onDataNotAvailable()
    }

    fun getTasks(callback: LoadTasksCallback)

    fun getTask(taskId: String, callback: GetTaskCallback)

    fun saveTask(task: Task)

    fun completeTask(task: Task)

    fun completeTask(taskId: String)

    fun activateTask(task: Task)

    fun activateTask(taskId: String)

    fun clearCompletedTasks()

    fun refreshTasks()

    fun deleteAllTasks()

    fun deleteTask(taskId: String)
}
复制代码

Repository都实现了TasksDataSource接口,并且包含有多个实现了TasksDataSource接口的数据,或是本地数据,或是缓存数据。并且只有getTasks和getTask这两个函数有回调方法,作为回调给ViewModel的数据接口。别的函数作为Model暴露给ViewModel去操作处理数据的函数。提高了通用性,可以让一个Repository去同时完成对缓存数据或者新数据的操作。

总结一下

使用MVVM后,确实对代码解耦产生了极好的效果,代码的可读性也上升的很多。文章里很多东西还是本人的一些想法, 以及碰到的一些问题并没有找到好的解决方案,同时在开发中,使用DataBinding之后代码的debug麻烦程度上升有点多,Model层的设计难度是我觉得最难得一个点,Google Sample里我觉得也有一些不太好的地方,之后我会再写一篇讨论一下Google的这个MVVM的Sample。

感谢各位的阅读,如果有什么想法,欢迎提出意见、批评。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

轻量级Django

轻量级Django

茱莉亚·埃尔曼 (Julia Elman)、马克·拉温 (Mark Lavin) / 侯荣涛、吴磊 / 中国电力出版社; 第1版 / 2016-11-1 / 35.6

自Django 创建以来,各种各样的开源社区已经构建了很多Web 框架,比如JavaScript 社区创建的Angular.js 、Ember.js 和Backbone.js 之类面向前端的Web 框架,它们是现代Web 开发中的先驱。Django 从哪里入手来适应这些框架呢?我们如何将客户端MVC 框架整合成为当前的Django 基础架构? 本书讲述如何利用Django 强大的“自支持”功......一起来看看 《轻量级Django》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具