随着对公司现有MVP架构模式代码的逐渐不满,每每新增一个小功能,总要在一个又一个的接口中新增方法,代码变得越来越不清晰,迭代变得越来越困难。作为一个爱搞事的程序猿,免不了要考虑一波新的架构模式,MVVM也就映入眼帘。首先对比一下我们曾经的MVP和即将要使用的MVVM吧(MVC就离开历史舞台吧)。
为什么要淘汰MVP?
- 太多的接口,一个功能处处接口
- Presenter层与View层耦合严重
- View层的一个改变往往牵动Presenter层,增加一个新方法往往要修改好几处地方
- 每个Presenter无法重用,通常耦合到特定的View层
MVVM有什么优势?
- 不再有过多的接口
- ViewModel和DataModel支持单元测试
- ViewModel不再与特定的View耦合
- ViewModel可以在多个View中组合或使用
MVVM到底长什么样?
View:
Activity/Fragment/View
从ViewModel层获取UI数据
请求ViewModel对数据进行操作
ViewModel:
作为View和Model之间的桥梁
请求Model层的数据并为View层转换
使View层更新数据
Model:
作为DataModel/Repository
持久化业务逻辑
从多种数据源获取数据(DataBase,REST Api,cache)
实际使用
这一篇中,我不打算使用LiveData,LiveData在接下来的学习中我们会使用,而且将会相当的给力。先试试RX来实现数据Observe吧。
基本实现
下面这个例子,将会简单的从API请求用户信息列表并显示。
/** ViewModel */
class UserListViewModel(val userRepository: UserRepository){
fun getUsers(): Observable<UserList> {
//获取用户数据
return userRepository.getUser()
.map { UserList(it, "Uses loaded success.") }
}
}
/** Model */
class UserRepository(val userApi: UserApi){
fun getUsers(): Observable<List<User>> =
userApi.getUsers()
}
interface UserApi{
@GET("users")
fun getUsers(): Observabel<List<User>>
}
UserListViewModle: ViewModel层,从Repository中获取数据,提供给View
UserRepository: 用户数据仓库,从数据库、网路Api获取数据
UserApi: 网路接口
改进Model层
我们可以修改UserRepository使它可以存储从API请求到的用户列表,这样每次我们调用getUser()方法中,我们会立即先返回缓存的用户列表,然后再返回API中的新数据。这可以使用RX中的mergeWith()方法来实现
class UserRepository(val userApi: UserApi){
var cacheUsers = emptyList<User>()
fun getUsers(): Observale<List<User>> =
if (cachedUsers.isEmpty())
userApi.getUsers().doOnNext{ cachedUsers = it }
else
Observable.just(cachedUsers)
.mergeWith(userApi.getUsers())
.doOnNext{ cachedUsers = it }
}
(PS: 通常在项目中,我们都会使用Dagger来提供UserRepository、UserApi的单例)
好处
这种方法中(将ViewModel与DataModel / Repository分开),ViewModel层不知道,也不关心数据的来源。它也不负责缓存或存储数据。我们能够在不对ViewModel进行任何更改的情况下引入API数据的缓存。
对UserRepository单元测试
我们可以使用TestObserver对UserRepository进行单元测试
class UserRepositoryTest{
lateinit var userRepository: UserRepository
lateinit var userApi: UserApi
@Before
fun setUp(){
userApi = mock()
userRepository = UserRepository(userApi)
}
@Test
fun test_emptyCache_noDataOnApi_returnEmptyList(){
`when`(userApi.getUsers()).thenReturn(Observable.just(emptyList<User>()))
userRepository.getUsers().test()
.assertValue{ it.isEmpty() }
}
@Test
fun test_emptyCache_hasDataOnApi_returnsApiData() {
`when`(userApi.getUsers()).thenReturn(Observable.just(listOf(aRandomUser())))
userRepository.getUsers().test()
.assertValueCount(1)
.assertValue { it.size == 1 }
}
@Test
fun test_hasCacheData_hasApiData_returnsBothData() {
val cachedData = listOf(aRandomUser())
val apiData = listOf(aRandomUser(), aRandomUser())
`when`(userApi.getUsers()).thenReturn(Observable.just(apiData))
userRepository.cachedUsers = cachedData
userRepository.getUsers().test()
//Both cached & API data delivered
.assertValueCount(2)
//First cache data delivered
.assertValueAt(0, { it == cachedData })
//Secondly api data delivered
.assertValueAt(1, { it == apiData })
}
@Test
fun test_cache_updatedWithApiData() {
val apiData = listOf(aRandomUser(), aRandomUser())
`when`(userApi.getUsers()).thenReturn(Observable.just(apiData))
userRepository.getUsers().test()
assertEquals(userRepository.cachedUsers, apiData)
}
fun aRandomUser() = User("mail@test.com", "John", UUID.randomUUID().toString().take(5))
}
}
总结
这只是我个人对Android中MVVM当前状态和新架构组件的看法。选择适合自己需求的架构,适合自己的团队结构,保持代码清洁,帮助您轻松测试代码,最重要的是允许您轻松添加新功能。