内容简介:公司的项目,一直是以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… 。 先引用一张来自朋友博客的图片,博客链接:博客链接
每个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。
感谢各位的阅读,如果有什么想法,欢迎提出意见、批评。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 初码-Azure系列-迁移PHP应用至Azure的一些实践记录和思考
- Go基础学习记录 - 编写Web应用程序 - 路由和程序启动的一些思考
- Go基础学习记录 - 编写Web应用程序 - 博客编辑功能之Model的重新思考
- 记录一次vue练习的填坑记录
- 【错误记录】git ssh 推送失败的一次记录
- 问题管理思考01(200506)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
CGI 程序设计自学通
(美)格里高利 / 徐丹/等 / 机械工业出版社 / 1998-08 / 28.00元
本书集中讨论CGI编程,以便利用一起来看看 《CGI 程序设计自学通》 这本书的介绍吧!