Android 开发过程当中,Model 层一般是比较薄弱的。获取数据的代码通过各类优秀的封装,已经能够简化到短短几行代码,对于简单的项目而言,全都写在 Activity/Fragment 中就是最合适的了,若是使用了 MVP 或者 MVVM 模式,也基本会把数据的获取放在 Presenter/ViewModel 中。(后面用业务逻辑层表示 Controller/Presenter/ViewModel)android
但 Model 层是很重要的,MVC,MVP,MVVM 甚至更复杂的架构模式,都须要 Model 层。最近为了作接口数据格式的自动化测试,又对 Model 层的实现进行了一次学习,本文记录了学习过程当中的一些问题以及我的理解。问题以下:git
说来惭愧,以前写过的代码都是在业务里作网络请求,处理回调数据。这种方法存在几个问题:github
而独立出 Model 层能够解决上述问题,使代码更易读且便于扩展,还能添加单元测试提升软件质量,对于 App 的长期发展有很大的好处。数据库
终于到了写代码的时间,此次参考的依然是 googlesamples/android-architecture。为了知足单元测试的需求,我将一个 demo 项目重构为 MVP 模式了。缓存
Model 层内部还须要再分层,将不一样来源(网络和本地)的数据获取代码分开。代码的结构大概这样:bash
业务层须要的数据获取定义成 DataSource 接口中的函数,固定参数和回调。具体实现为 LocalDataSource 和 RemoteDataSource 等。Repository 实现 DataSource 并持有具体的一种或多种 DataSource,经过组合不一样的 DataSource 实现获取数据的功能。网络
某 App 首页须要经过网络获取一个列表数据来展现,为了更好的用户体验,每次刷新的列表会缓存在本地。这样在请求成功前就不是空白的页面了。架构
interface ExDataSource {
interface LoadListCallback{
fun onSuccess(list: ArrayList<ListItemBean>)
fun onError(errorCode: Int, errorMsg: String)
}
fun loadList(page: Int, callback: LoadListCallback)
}
复制代码
而后分别实现具体的数据获取方法:框架
// 对数据处理的函数能够以静态方法的方式提到外面
class ExRemoteDataSource: ExDataSource{
override fun loadList(page: Int, callback: LoadListCallback){
// 发起网络请求,解析返回数据,若是不能解析成ArrayList<ListItemBean>也回调失败
// 具体代码实现与网络请求框架有关,此处不放代码了
}
}
···
class ExLocalDataSource: ExDataSource{
override fun loadList(page: Int, callback: LoadListCallback){
// 从数据库或者文件获取缓存的数据
}
fun setCacheList(list: ArrayList<ListItemBean>){
// 更新缓存内容
}
}
复制代码
最后在 Repository 中处理缓存逻辑:ide
//
class ExRepositiry(
private val remoteDataSource: ExRemoteDataSource,
private val localDataSource: ExLocalDataSource
): ExDataSource {
override fun loadList(page: Int, callback: LoadListCallback){
//具体如何使用缓存跟需求有关,这里简化写一下
// 先加载本地数据作显示
localDataSource.loadList(page, callback)
// 同时发起网络请求
remoteDataSource.loadList(page, object: LoadListCallback{
override fun onSuccess(list: ArrayList<ListItemBean>){
// 成功后更新缓存,刷新页面
localDataSource.setCacheList(list)
callback.onSuccess(list)
}
override fun onError(errorCode: Int, errorMsg: String){
callback.onError(errorCode, errorMsg)
}
})
}
}
复制代码
业务层只须要建立 Repository 就能得到想要的数据了,对于错误的状况,就详细规划 onError 的回调,再根据具体需求处理。
接口回调是没法从根本上取代的,若是为了代码简明,能够建立几个泛型接口模板来避免每一个请求对应一个接口。使用 Kotlin 的话能够直接按成功和失败传入函数:
···
fun loadList(
page: Int,
onSuccess: (ArrayList<ListItemBean>) -> Unit,
onError: (errorCode: Int, errorMsg: String) -> Unit
)
···
复制代码
随着项目的发展,须要的数据会愈来愈多,都写在同一个 Repository 中获取数据会让代码的可读性降低。应该按照业务将 Repository 模块化,以适应将来项目的模块化和组件化。(不须要分得太细碎,Repository 自己由多个独立的数据获取代码构成,即便有不少行也能保证逻辑清晰)
算了一下内容,再写下去就太长了。并且关于单元测试的部分还没应用到项目中,不肯定还有没有坑,最后这个问题下周单独写一篇吧。
Model 层能够说是欠了好久的技术债了,最初以为只是几行代码的网络请求拆出来也没有意义,随着业务的发展,出现了不少须要缓存的页面,就也把取本地的数据的代码写在业务逻辑中了。如今要保证复杂逻辑代码的稳定性,想要添加单元测试,再回头看代码才明白已经走偏了太多。
代码的架构应该是分层,而不是分块。不少代码中把一部分业务逻辑委托到一个 xxManager 去作,表面上彷佛单个文件中的代码少了,但并不符合单一职责原则,实际上代码的可读性仍是很差,后续维护也依然麻烦。
从事 Android App 开发快 2 年了,我居然还没写过单元测试,实际上是很无奈的一件事。不写测试的理由可能有不少,但写单元测试的理由只有为了更高的代码质量。为了让代码可以长期维护下去,解耦和单元测试都是很是重要的。
那么你开始写单元测试了吗?