关于 Model 层的几点思考(一)

栏目: 编程工具 · 发布时间: 6年前

内容简介:Android 开发过程中,Model 层通常是比较薄弱的。获取数据的代码经过各种优秀的封装,已经可以简化到短短几行代码,对于简单的项目而言,全都写在 Activity/Fragment 中就是最合适的了,如果使用了 MVP 或者 MVVM 模式,也基本会把数据的获取放在 Presenter/ViewModel 中。(后面用业务逻辑层表示 Controller/Presenter/ViewModel)但 Model 层是很重要的,MVC,MVP,MVVM 甚至更复杂的架构模式,都需要 Model 层。最近为

Android 开发过程中,Model 层通常是比较薄弱的。获取数据的代码经过各种优秀的封装,已经可以简化到短短几行代码,对于简单的项目而言,全都写在 Activity/Fragment 中就是最合适的了,如果使用了 MVP 或者 MVVM 模式,也基本会把数据的获取放在 Presenter/ViewModel 中。(后面用业务逻辑层表示 Controller/Presenter/ViewModel)

但 Model 层是很重要的,MVC,MVP,MVVM 甚至更复杂的架构模式,都需要 Model 层。最近为了做接口数据格式的自动化测试,又对 Model 层的实现进行了一次学习,本文记录了学习过程中的一些问题以及个人理解。问题如下:

  • 为什么数据获取不应该写在业务逻辑里?
  • Model 层应该包括什么内容?
  • 如何构造 Model 层?
  • 如何以 Model 为界线,分别对业务层和数据层做自动化测试?

问题一:为什么数据获取不应该写在业务逻辑里?

说来惭愧,之前写过的代码都是在业务里做网络请求,处理回调数据。这种方法存在几个问题:

  1. 多个页面请求同一接口时,处理代码会重复
  2. 添加数据缓存功能会影响业务代码
  3. 无法进行单元测试

而独立出 Model 层可以解决上述问题,使代码更易读且便于扩展,还能添加单元测试提高软件质量,对于 App 的长期发展有很大的好处。

问题二:Model 层应该包括什么内容?

  1. 数据获取的方法:包括网络请求和读取本地文件、数据库等
  2. 数据处理的方法:Model 层提供的数据应该是业务中直接可用的,这样就能在测试中区分开错误的来源是数据还是逻辑 bug
  3. 实体类

问题三:如何构造 Model 层?

终于到了写代码的时间,这次参考的依然是 googlesamples/android-architecture 。为了满足单元测试的需求,我将一个 demo 项目重构为 MVP 模式了。

Model 层内部还需要再分层,将不同来源(网络和本地)的数据获取代码分开。代码的结构大概这样:

关于 Model 层的几点思考(一)

业务层需要的数据获取定义成 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 中处理缓存逻辑:

//
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 的回调,再根据具体需求处理。

  • 问题3.1:如何避免创建大量回调接口?

接口回调是无法从根本上取代的,如果为了代码简明,可以创建几个泛型接口模板来避免每个请求对应一个接口。使用 Kotlin 的话可以直接按成功和失败传入函数:

···
    fun loadList(
        page: Int, 
        onSuccess: (ArrayList<ListItemBean>) -> Unit, 
        onError: (errorCode: Int, errorMsg: String) -> Unit
    )
···
复制代码
  • 问题3.2:如何划分 Repository?

随着项目的发展,需要的数据会越来越多,都写在同一个 Repository 中获取数据会让代码的可读性下降。应该按照业务将 Repository 模块化,以适应未来项目的模块化和组件化。(不需要分得太细碎,Repository 本身由多个独立的数据获取代码构成,即使有很多行也能保证逻辑清晰)

问题四:如何以 Model 为界线,分别对业务层和数据层做自动化测试?

算了一下内容,再写下去就太长了。而且关于单元测试的部分还没应用到项目中,不确定还有没有坑,最后这个问题下周单独写一篇吧。

总结

Model 层可以说是欠了很久的技术债了,最初觉得只是几行代码的网络请求拆出来也没有意义,随着业务的发展,出现了很多需要缓存的页面,就也把取本地的数据的代码写在业务逻辑中了。现在要保证复杂逻辑代码的稳定性,想要添加单元测试,再回头看代码才明白已经走偏了太多。

代码的架构应该是分层,而不是分块。很多代码中把一部分业务逻辑委托到一个 xxManager 去做,表面上似乎单个文件中的代码少了,但并不符合单一职责原则,实际上代码的可读性还是不好,后续维护也依然麻烦。

从事 Android App 开发快 2 年了,我竟然还没写过单元测试,其实是很无奈的一件事。不写测试的理由可能有很多,但写单元测试的理由只有为了更高的代码质量。为了让代码能够长期维护下去,解耦和单元测试都是非常重要的。

那么你开始写单元测试了吗?


以上所述就是小编给大家介绍的《关于 Model 层的几点思考(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Essential ActionScript 3.0

Essential ActionScript 3.0

Colin Moock / Adobe Dev Library / June 22, 2007 / $34.64

ActionScript 3.0 is a huge upgrade to Flash's programming language. The enhancements to ActionScript's performance, feature set, ease of use, cleanliness, and sophistication are considerable. Essentia......一起来看看 《Essential ActionScript 3.0》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具